diff --git a/CultMath.slnx b/CultMath.slnx
index 05d1788..9583457 100644
--- a/CultMath.slnx
+++ b/CultMath.slnx
@@ -5,7 +5,4 @@
-
-
-
diff --git a/README.md b/README.md
index c60a991..b471c7f 100644
--- a/README.md
+++ b/README.md
@@ -30,12 +30,6 @@ shader semantics.
- `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.
-- `PlanetaryField`, a scale-aware CPU/HLSL spherical field contract with
- deterministic identity, cube-sphere topology, residual pages, LOD residency,
- ray/path/region queries, coarse patches, and projected map-tile baking. See
- `docs/planetary.md`.
-- `CultMath.Unity`, an engine-facing assembly containing only Unity mesh and
- page-upload adapters for the canonical planetary contracts.
## Native Core
@@ -72,11 +66,6 @@ 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.
-The advanced erosion filter is an MPL-2.0 adaptation of Rune Skovbo Johansen's
-published filter. Its faithful planar core is isolated in
-`AdvancedErosionFilter.cs` and `AdvancedErosionFilter.hlsl`; spherical mapping
-is a separate CultMath concern. See `THIRD-PARTY-NOTICES.md`.
-
## Build
```powershell
diff --git a/Samples~/PlanetaryViewer/Editor/CultMath.PlanetaryViewer.Editor.asmdef b/Samples~/PlanetaryViewer/Editor/CultMath.PlanetaryViewer.Editor.asmdef
deleted file mode 100644
index 1051232..0000000
--- a/Samples~/PlanetaryViewer/Editor/CultMath.PlanetaryViewer.Editor.asmdef
+++ /dev/null
@@ -1,18 +0,0 @@
-{
- "name": "CultMath.PlanetaryViewer.Editor",
- "rootNamespace": "CultMath.Unity.Samples.Editor",
- "references": [
- "CultMath.PlanetaryViewer"
- ],
- "includePlatforms": [
- "Editor"
- ],
- "excludePlatforms": [],
- "allowUnsafeCode": false,
- "overrideReferences": false,
- "precompiledReferences": [],
- "autoReferenced": true,
- "defineConstraints": [],
- "versionDefines": [],
- "noEngineReferences": false
-}
diff --git a/Samples~/PlanetaryViewer/Editor/CultMathPlanetaryViewerMenu.cs b/Samples~/PlanetaryViewer/Editor/CultMathPlanetaryViewerMenu.cs
deleted file mode 100644
index ade28c3..0000000
--- a/Samples~/PlanetaryViewer/Editor/CultMathPlanetaryViewerMenu.cs
+++ /dev/null
@@ -1,26 +0,0 @@
-using UnityEditor;
-using UnityEngine;
-
-namespace CultMath.Unity.Samples.Editor;
-
-public static class CultMathPlanetaryViewerMenu
-{
- [MenuItem("GameObject/CultMath/Planetary Field Viewer", false, 10)]
- public static void CreateViewer()
- {
- var root = new GameObject("CultMath Planetary Field Viewer");
- Undo.RegisterCreatedObjectUndo(root, "Create CultMath Planetary Viewer");
- root.AddComponent();
- Selection.activeGameObject = root;
-
- if (Camera.main != null) return;
- var cameraObject = new GameObject("Planetary Viewer Camera");
- Undo.RegisterCreatedObjectUndo(cameraObject, "Create Planetary Viewer Camera");
- cameraObject.tag = "MainCamera";
- cameraObject.transform.position = new Vector3(0, 0, -24);
- cameraObject.transform.LookAt(root.transform);
- var camera = cameraObject.AddComponent();
- camera.nearClipPlane = 0.01f;
- camera.farClipPlane = 1000;
- }
-}
diff --git a/Samples~/PlanetaryViewer/README.md b/Samples~/PlanetaryViewer/README.md
deleted file mode 100644
index ec4dcc3..0000000
--- a/Samples~/PlanetaryViewer/README.md
+++ /dev/null
@@ -1,16 +0,0 @@
-# Planetary Field Viewer
-
-Import this sample through Unity's Package Manager, then choose
-`GameObject > CultMath > Planetary Field Viewer`.
-
-The host project must contain `Assets/csc.rsp` with
-`-langversion:latest`, as documented in the CultMath package README.
-
-The generated six-face mesh uses `PlanetaryPatch`. Its shader calls
-`cultmath_planetary_field_sample` from the packaged CultMath HLSL, while the
-component exposes the matching CPU `Sample` method. Seed, radius, physical
-sample footprint, erosion settings, and base-field equations are shared between
-the two bodies.
-
-This is a renderer integration fixture, not a gameplay owner. It deliberately
-contains no Aetheria state, biomes, resources, or simulation decisions.
diff --git a/Samples~/PlanetaryViewer/Runtime/CultMath.PlanetaryViewer.asmdef b/Samples~/PlanetaryViewer/Runtime/CultMath.PlanetaryViewer.asmdef
deleted file mode 100644
index 2d3676f..0000000
--- a/Samples~/PlanetaryViewer/Runtime/CultMath.PlanetaryViewer.asmdef
+++ /dev/null
@@ -1,17 +0,0 @@
-{
- "name": "CultMath.PlanetaryViewer",
- "rootNamespace": "CultMath.Unity.Samples",
- "references": [
- "CultMath",
- "CultMath.Unity"
- ],
- "includePlatforms": [],
- "excludePlatforms": [],
- "allowUnsafeCode": false,
- "overrideReferences": false,
- "precompiledReferences": [],
- "autoReferenced": true,
- "defineConstraints": [],
- "versionDefines": [],
- "noEngineReferences": false
-}
diff --git a/Samples~/PlanetaryViewer/Runtime/CultMathPlanetaryViewer.cs b/Samples~/PlanetaryViewer/Runtime/CultMathPlanetaryViewer.cs
deleted file mode 100644
index 0611372..0000000
--- a/Samples~/PlanetaryViewer/Runtime/CultMathPlanetaryViewer.cs
+++ /dev/null
@@ -1,139 +0,0 @@
-using System;
-using System.Collections.Generic;
-using UnityEngine;
-
-namespace CultMath.Unity.Samples;
-
-[ExecuteAlways]
-public sealed class CultMathPlanetaryViewer : MonoBehaviour
-{
- [Min(0.001f)] public float Radius = 8.4f;
- [Range(1, 256)] public int CellsPerFace = 64;
- public int Seed = 42;
- [Min(0.0001f)] public float SampleFootprint = 0.01f;
- public Shader? PlanetShader;
- public Vector3 LightDirection = new(-0.4f, 0.7f, -0.6f);
- public Color LowColor = new(0.055f, 0.16f, 0.09f, 1);
- public Color HighColor = new(0.62f, 0.48f, 0.28f, 1);
- public Color RidgeColor = new(0.88f, 0.83f, 0.7f, 1);
-
- private readonly List meshes = new();
- private Material? material;
- private int builtCells;
-
- public PlanetaryFieldDefinition Definition => PlanetaryFieldDefinition.Create(
- 1,
- Radius,
- Seed,
- ViewerErosion(Radius));
-
- public PlanetarySurfaceSample Sample(float3 direction)
- {
- var source = new ViewerBaseField(Radius);
- var normalized = math.normalize(direction);
- return PlanetaryField.Sample(Definition, normalized, source.Sample(normalized), PlanetaryQueryScale.AtFootprint(SampleFootprint));
- }
-
- private void OnEnable() => Rebuild();
-
- private void OnValidate()
- {
- Radius = Mathf.Max(Radius, 0.001f);
- CellsPerFace = Mathf.Clamp(CellsPerFace, 1, 256);
- SampleFootprint = Mathf.Max(SampleFootprint, 0.0001f);
- if (isActiveAndEnabled) Rebuild();
- }
-
- private void Update()
- {
- if (builtCells != CellsPerFace || material == null) Rebuild();
- ApplyMaterial();
- }
-
- private void Rebuild()
- {
- ClearGenerated();
- PlanetShader ??= Shader.Find("CultMath/Planetary Field Viewer");
- if (PlanetShader == null) return;
- material = new Material(PlanetShader) { name = "CultMath Planetary Viewer Material", hideFlags = HideFlags.HideAndDontSave };
- foreach (var face in (PlanetaryCubeFace[])Enum.GetValues(typeof(PlanetaryCubeFace)))
- {
- var mesh = PlanetaryPatchMeshAdapter.CreateFaceMesh(face, CellsPerFace);
- mesh.hideFlags = HideFlags.HideAndDontSave;
- meshes.Add(mesh);
- var child = new GameObject(face.ToString()) { hideFlags = HideFlags.DontSave };
- child.transform.SetParent(transform, false);
- child.AddComponent().sharedMesh = mesh;
- child.AddComponent().sharedMaterial = material;
- }
- builtCells = CellsPerFace;
- ApplyMaterial();
- }
-
- private void ApplyMaterial()
- {
- if (material == null) return;
- var p = ViewerErosion(Radius);
- material.SetFloat("_Radius", Radius);
- material.SetFloat("_SampleFootprint", SampleFootprint);
- material.SetInt("_Seed", Seed);
- material.SetFloat("_ErosionScale", p.Scale);
- material.SetFloat("_ErosionStrength", p.Strength);
- material.SetFloat("_GullyWeight", p.GullyWeight);
- material.SetFloat("_Detail", p.Detail);
- material.SetVector("_Rounding", new(p.Rounding.x, p.Rounding.y, p.Rounding.z, p.Rounding.w));
- material.SetVector("_Onset", new(p.Onset.x, p.Onset.y, p.Onset.z, p.Onset.w));
- material.SetVector("_AssumedSlope", new(p.AssumedSlope.x, p.AssumedSlope.y, 0, 0));
- material.SetFloat("_CellScale", p.CellScale);
- material.SetFloat("_Normalization", p.Normalization);
- material.SetInt("_Octaves", p.Octaves);
- material.SetFloat("_Lacunarity", p.Lacunarity);
- material.SetFloat("_Gain", p.Gain);
- material.SetVector("_LightDirection", LightDirection.normalized);
- material.SetColor("_LowColor", LowColor);
- material.SetColor("_HighColor", HighColor);
- material.SetColor("_RidgeColor", RidgeColor);
- }
-
- private void OnDisable() => ClearGenerated();
- private void OnDestroy() => ClearGenerated();
-
- private void ClearGenerated()
- {
- for (var i = transform.childCount - 1; i >= 0; i--) DestroyGenerated(transform.GetChild(i).gameObject);
- foreach (var mesh in meshes) DestroyGenerated(mesh);
- meshes.Clear();
- if (material != null) DestroyGenerated(material);
- material = null;
- builtCells = 0;
- }
-
- private static void DestroyGenerated(UnityEngine.Object value)
- {
- if (Application.isPlaying) Destroy(value);
- else DestroyImmediate(value);
- }
-
- private static AdvancedErosionParameters ViewerErosion(float radius) => new(
- radius * 0.075f, 0.12f, 0.58f, 1.45f,
- new float4(0.1f, 0.015f, 0.1f, 2),
- new float4(1.25f, 1.25f, 2.8f, 1.5f),
- new float2(0.7f, 0.85f), 0.7f, 0.5f, 7, 2, 0.5f);
-
- private readonly struct ViewerBaseField : IPlanetaryBaseField
- {
- private readonly float radius;
- public ViewerBaseField(float radius) => this.radius = radius;
-
- public PlanetaryBaseFieldSample Sample(float3 direction)
- {
- var value = direction.x * 0.6f + direction.y * direction.z * 0.3f;
- var angularGradient = new float3(0.6f, direction.z * 0.3f, direction.y * 0.3f);
- angularGradient -= direction * math.dot(angularGradient, direction);
- var fieldGradient = angularGradient / radius;
- var radialDisplacement = value * radius * 0.035f;
- var radialGradient = angularGradient * 0.035f;
- return new(radialDisplacement, radialGradient, value, fieldGradient, math.clamp(value, -1, 1));
- }
- }
-}
diff --git a/Samples~/PlanetaryViewer/Runtime/CultMathPlanetaryViewer.shader b/Samples~/PlanetaryViewer/Runtime/CultMathPlanetaryViewer.shader
deleted file mode 100644
index b5f7274..0000000
--- a/Samples~/PlanetaryViewer/Runtime/CultMathPlanetaryViewer.shader
+++ /dev/null
@@ -1,90 +0,0 @@
-Shader "CultMath/Planetary Field Viewer"
-{
- Properties
- {
- _LowColor ("Low Color", Color) = (0.055, 0.16, 0.09, 1)
- _HighColor ("High Color", Color) = (0.62, 0.48, 0.28, 1)
- _RidgeColor ("Ridge Color", Color) = (0.88, 0.83, 0.7, 1)
- }
- SubShader
- {
- Tags { "RenderType"="Opaque" "Queue"="Geometry" }
- Pass
- {
- Cull Back
- ZWrite On
- HLSLPROGRAM
- #pragma target 5.0
- #pragma vertex vert
- #pragma fragment frag
- #include "UnityCG.cginc"
- #include "Packages/org.gamecult.cultmath/shaders/CultMath.hlsl"
-
- float _Radius;
- float _SampleFootprint;
- int _Seed;
- float _ErosionScale, _ErosionStrength, _GullyWeight, _Detail;
- float4 _Rounding, _Onset, _AssumedSlope;
- float _CellScale, _Normalization, _Lacunarity, _Gain;
- int _Octaves;
- float3 _LightDirection;
- float4 _LowColor, _HighColor, _RidgeColor;
-
- struct Attributes { float4 vertex : POSITION; };
- struct Varyings
- {
- float4 position : SV_POSITION;
- float3 worldNormal : TEXCOORD0;
- float height : TEXCOORD1;
- float ridge : TEXCOORD2;
- };
-
- CultMathPlanetarySurfaceSample viewer_sample(float3 direction)
- {
- direction = normalize(direction);
- float value = direction.x * 0.6 + direction.y * direction.z * 0.3;
- float3 angular_gradient = float3(0.6, direction.z * 0.3, direction.y * 0.3);
- angular_gradient -= direction * dot(angular_gradient, direction);
- CultMathPlanetaryBaseFieldSample base_sample;
- base_sample.radial_displacement = value * _Radius * 0.035;
- base_sample.radial_gradient = angular_gradient * 0.035;
- base_sample.field_value = value;
- base_sample.field_gradient = angular_gradient / _Radius;
- base_sample.fade_target = clamp(value, -1.0, 1.0);
- CultMathAdvancedErosionParameters erosion;
- erosion.scale = _ErosionScale; erosion.strength = _ErosionStrength;
- erosion.gully_weight = _GullyWeight; erosion.detail = _Detail;
- erosion.rounding = _Rounding; erosion.onset = _Onset;
- erosion.assumed_slope = _AssumedSlope.xy; erosion.cell_scale = _CellScale;
- erosion.normalization = _Normalization; erosion.octaves = _Octaves;
- erosion.lacunarity = _Lacunarity; erosion.gain = _Gain;
- CultMathPlanetaryFieldDefinition definition;
- definition.radius = _Radius; definition.seed = _Seed; definition.erosion = erosion;
- return cultmath_planetary_field_sample(definition, direction, base_sample, _SampleFootprint);
- }
-
- Varyings vert(Attributes input)
- {
- float3 direction = normalize(input.vertex.xyz);
- CultMathPlanetarySurfaceSample sample = viewer_sample(direction);
- float3 local = direction * (_Radius + sample.radial_displacement);
- Varyings output;
- output.position = UnityObjectToClipPos(float4(local, 1));
- output.worldNormal = UnityObjectToWorldNormal(sample.surface_normal);
- output.height = sample.radial_displacement / max(_Radius * 0.1, 1.0e-5);
- output.ridge = sample.ridge;
- return output;
- }
-
- float4 frag(Varyings input) : SV_Target
- {
- float height = saturate(input.height * 0.5 + 0.5);
- float3 albedo = lerp(_LowColor.rgb, _HighColor.rgb, height);
- albedo = lerp(albedo, _RidgeColor.rgb, saturate((input.ridge - 0.55) * 2.5));
- float diffuse = saturate(dot(normalize(input.worldNormal), normalize(-_LightDirection))) * 0.82 + 0.08;
- return float4(albedo * diffuse, 1);
- }
- ENDHLSL
- }
- }
-}
diff --git a/THIRD-PARTY-NOTICES.md b/THIRD-PARTY-NOTICES.md
index 54e280f..c283df2 100644
--- a/THIRD-PARTY-NOTICES.md
+++ b/THIRD-PARTY-NOTICES.md
@@ -9,26 +9,3 @@ Gustavson's `webgl-noise` project and Unity Mathematics.
- Copyright 2011 Ashima Arts.
- Upstream:
- License: MIT.
-
-## Advanced Terrain Erosion Filter
-
-`src/CultMath/AdvancedErosionFilter.cs` and
-`shaders/AdvancedErosionFilter.hlsl` adapt the mathematical procedure from
-Rune Skovbo Johansen's **Advanced Terrain Erosion Filter**:
-
-- Copyright 2025 Rune Skovbo Johansen.
-- Upstream:
-- Explanation:
-- License: Mozilla Public License 2.0,
-
-
-The CultMath files retain the MPL notice and separate the faithful planar
-filter from CultMath's spherical adaptation. Modifications include C#/HLSL
-type lowering, explicit bounded octave counts, finite zero-rounding behavior,
-and CultMath naming. CultMath replaces the reference floating hash with a
-shared integer avalanche hash so C# and GPU evaluation select identical cell
-pivots across devices; this changes the stochastic pattern, not the filter
-mechanics.
-
-The technique builds on earlier work credited by the upstream author to Fewes
-and Clay John. The hash form follows the public shader math of Inigo Quilez.
diff --git a/Tools~/CultMath.PlanetaryTileFixture/CultMath.PlanetaryTileFixture.csproj b/Tools~/CultMath.PlanetaryTileFixture/CultMath.PlanetaryTileFixture.csproj
deleted file mode 100644
index 9a6f8bd..0000000
--- a/Tools~/CultMath.PlanetaryTileFixture/CultMath.PlanetaryTileFixture.csproj
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
- Exe
- net10.0
- enable
- enable
-
-
-
-
-
diff --git a/Tools~/CultMath.PlanetaryTileFixture/Program.cs b/Tools~/CultMath.PlanetaryTileFixture/Program.cs
deleted file mode 100644
index 1952fa2..0000000
--- a/Tools~/CultMath.PlanetaryTileFixture/Program.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-using CultMath;
-
-if (args.Length != 1) throw new ArgumentException("Expected one output path.");
-var field = PlanetaryFieldDefinition.Create(0x55aa, 10, 7, AdvancedErosionParameters.Default);
-var layout = new PlanetaryMapTileLayout(new(PlanetaryProjectionKind.EqualEarth), 0, 0, 0, 5, 1);
-var key = new PlanetaryMapTileKey(field.FieldVersion, 3, 29, layout, PlanetaryQueryScale.AtFootprint(0.25f, 0.125f));
-var tile = PlanetaryMapTileBaker.Bake(field, key, new FixtureField());
-var path = Path.GetFullPath(args[0]);
-Directory.CreateDirectory(Path.GetDirectoryName(path)!);
-using var stream = File.Create(path);
-PlanetaryMapTileEncoding.Write(stream, tile);
-
-readonly struct FixtureField : IPlanetaryBaseField
-{
- public PlanetaryBaseFieldSample Sample(float3 direction)
- {
- var value = direction.x * 0.2f + direction.z * 0.4f;
- var gradient = new float3(0.2f, 0, 0.4f);
- gradient -= direction * math.dot(gradient, direction);
- return new(value, gradient, value, gradient * 0.1f, value);
- }
-}
diff --git a/docs/design.md b/docs/design.md
index f4ca5b4..8250ba7 100644
--- a/docs/design.md
+++ b/docs/design.md
@@ -19,12 +19,6 @@ Rust hot kernels, C# host code, and HLSL shader code.
- 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.
-- Planetary geometry is a scale-aware field query, not a renderer asset. CPU
- simulation is the reference consumer; pages, patches, and maps are derived
- lowerings carrying field identity and unresolved error.
-- Application base fields and gameplay meaning enter through narrow inputs.
- CultMath does not own biomes, resources, traversability, render materials, or
- committed game consequences.
## Non-Goals
diff --git a/docs/planetary.md b/docs/planetary.md
deleted file mode 100644
index 3503429..0000000
--- a/docs/planetary.md
+++ /dev/null
@@ -1,130 +0,0 @@
-# Planetary Field Runtime
-
-CultMath owns the deterministic, renderer-independent planetary field. The same
-definition can be queried by a CPU simulation, baked into residual pages,
-sampled through a map projection, or evaluated by HLSL. Render resources and
-gameplay interpretation remain consumer responsibilities.
-
-## Authority
-
-- `PlanetaryFieldDefinition` identifies radius, seed, erosion parameters, and a
- version that includes the consumer-owned base-field version.
-- `IPlanetaryBaseField` supplies deterministic application-authored base
- evidence without making CultMath depend on an application.
-- `PlanetaryQueryScale` makes the physical footprint and accepted unresolved
- error explicit. There is deliberately no scale-free authoritative
- `HeightAt` call.
-- `PlanetarySurfaceSample` carries displacement, tangent gradient, normal,
- slope, ridge/gully evidence, resolved wavelength, and unresolved bound.
-- pages, residency, patches, and map tiles are disposable lowerings of that
- contract.
-
-The C# surface targets .NET and Unity-compatible `netstandard2.1`. The matching
-HLSL lives in `shaders/Planetary.hlsl` and is included by `CultMath.hlsl`.
-
-## Point and batch queries
-
-```csharp
-var erosion = AdvancedErosionParameters.Default with
-{
- Scale = radius * 0.075f,
- Strength = 0.12f,
- Octaves = 7,
-};
-var field = PlanetaryFieldDefinition.Create(
- baseFieldVersion: 17,
- radius,
- seed: 42,
- erosion);
-var scale = PlanetaryQueryScale.AtFootprint(
- footprintMeters: 10,
- maximumUnresolvedHeight: 0.5f);
-
-PlanetaryBaseFieldSample basis = source.Sample(direction);
-PlanetarySurfaceSample sample = PlanetaryField.Sample(
- field, direction, basis, scale);
-```
-
-`PlanetaryField.SampleBatch` writes into a caller-owned span. `SamplePosition`
-accepts `double3`, reduces the astronomical position to a stable unit direction,
-and then evaluates the same float field contract.
-
-Higher-level CPU queries include bounded ray intersection, segment clearance,
-great-circle profiles, and region summaries. Their answers remain field queries;
-only a consuming simulation can turn them into committed game consequences.
-
-## Cube-sphere topology and pages
-
-`PlanetaryTopology` owns the tangent/QSC cube-sphere mapping, inverse face
-coordinates, tile selection, local page coordinates, and radial-graph surface
-normal. `PlanetaryPageSampling` owns bordered directions, nominal spacing,
-bilinear sampling, and summaries.
-
-`PlanetaryPageBaker` evaluates a root page at its physical sample spacing. A
-child stores `child filtered field - parent filtered field`. A
-`PlanetaryPageSetSampling` query adds every containing ancestor contribution
-and applies transition weights only to residuals.
-
-`PlanetaryLodSelector` selects six roots plus one camera-facing ancestor chain
-from the unresolved-height error. `PlanetaryResidualResidency` owns arrival and
-departure presentation weights, content and presentation versions, and nothing
-about field truth.
-
-## Patches
-
-`PlanetaryPatch.CreateFace` and `CreateCubeSphere` produce renderer-neutral
-coarse meshes from the canonical topology. HLSL calls
-`cultmath_planetary_face_direction` for procedural vertices and
-`cultmath_planetary_radial_refinement_step` for bounded pixel refinement.
-
-Renderers still own cameras, clip space, depth, materials, buffers, dispatch,
-and draw submission.
-
-`PlanetaryGpuPageBuilder` owns the renderer-neutral page input and metadata
-payload. Host adapters may convert its `float4` values to native vector types,
-but do not recalculate directions, spacing, addressing, or transition state.
-
-The optional `CultMath.Unity` assembly converts a canonical face patch into a
-Unity `Mesh` and converts the shared GPU payload into Unity-native structs. It
-owns no field parameters, page selection, or simulation state. The package's
-Planetary Field Viewer sample renders the shared HLSL field and reports matching
-CPU queries. Unity render code remains responsible for allocating/binding
-graphics buffers and dispatching application-specific base-field entry points.
-The host Unity project must set `-langversion:latest` in `Assets/csc.rsp`.
-
-## Maps
-
-`PlanetaryProjection` supplies forward and inverse transforms for:
-
-- equirectangular;
-- Web Mercator with an explicit polar cutoff;
-- Equal Earth;
-- orthographic;
-- azimuthal equidistant and equal-area;
-- cube atlas;
-- local tangent/gnomonic views.
-
-`PlanetaryMapTileBaker` maps each valid tile sample through the inverse
-projection and canonical field query. `PlanetaryMapTileKey` includes field,
-projection, layer, layout, and query-scale identity. Palettes, contours, labels,
-and strategic overlays do not enter CultMath.
-
-`PlanetaryMapTileEncoding` writes the resulting evidence as a versioned
-little-endian `CMPT` binary asset. `web/planetary-tile.ts` decodes that exact
-format into browser-native values without defining another terrain generator.
-Typed CultMesh documents should carry the tile key and asset reference; the
-binary payload is derived CDN/cache data, not committed game state.
-
-## Numeric contract
-
-Integer hashes and published field versions are exact. Basic arithmetic follows
-CultMath's matched C#/HLSL semantics. Hardware transcendental functions such as
-`atan` and `tan` use measured tolerances rather than fictional bit identity.
-Every filtered terrain query carries an unresolved-height bound so consumers can
-reject insufficient fidelity explicitly.
-
-Current D3D12 parity evidence covers the advanced erosion kernel, QSC topology,
-and forward/inverse transforms for every supported map projection, including
-center and scale parameters. Production page and patch shaders use the common
-functions and retain their seam, residual, summary, and radial-hit integration
-probes in Fensalir.
diff --git a/docs/planetary.md.meta b/docs/planetary.md.meta
deleted file mode 100644
index af364c2..0000000
--- a/docs/planetary.md.meta
+++ /dev/null
@@ -1,7 +0,0 @@
-fileFormatVersion: 2
-guid: 23a5751e4cececc4e99e024058d69add
-TextScriptImporter:
- externalObjects: {}
- userData:
- assetBundleName:
- assetBundleVariant:
diff --git a/package.json b/package.json
index 2564360..6da2a17 100644
--- a/package.json
+++ b/package.json
@@ -10,12 +10,5 @@
"Simulation"
],
"license": "MPL-2.0",
- "category": "Scripting",
- "samples": [
- {
- "displayName": "Planetary Field Viewer",
- "description": "Runnable Unity viewer using the canonical CultMath CPU and HLSL planetary field.",
- "path": "Samples~/PlanetaryViewer"
- }
- ]
+ "category": "Scripting"
}
diff --git a/scripts/test-web-planetary-tile.ps1 b/scripts/test-web-planetary-tile.ps1
deleted file mode 100644
index 5221fb3..0000000
--- a/scripts/test-web-planetary-tile.ps1
+++ /dev/null
@@ -1,9 +0,0 @@
-$ErrorActionPreference = "Stop"
-$root = Split-Path -Parent $PSScriptRoot
-$fixture = Join-Path $root ".tools\planetary-tile\fixture.cmpt"
-dotnet run --project (Join-Path $root "Tools~\CultMath.PlanetaryTileFixture\CultMath.PlanetaryTileFixture.csproj") -- $fixture
-if ($LASTEXITCODE -ne 0) { throw "Planetary tile fixture generation failed." }
-node --no-warnings --experimental-strip-types (Join-Path $root "web\planetary-tile.cross-runtime.test.mjs") $fixture
-if ($LASTEXITCODE -ne 0) { throw "Planetary tile web decoding failed." }
-node --no-warnings --experimental-strip-types (Join-Path $root "web\planetary-tile.test.mjs")
-if ($LASTEXITCODE -ne 0) { throw "Planetary tile decoder unit test failed." }
diff --git a/scripts/test-web-planetary-tile.ps1.meta b/scripts/test-web-planetary-tile.ps1.meta
deleted file mode 100644
index ffd7323..0000000
--- a/scripts/test-web-planetary-tile.ps1.meta
+++ /dev/null
@@ -1,7 +0,0 @@
-fileFormatVersion: 2
-guid: 235e8f203bd1c4d40a8fa211273106ba
-DefaultImporter:
- externalObjects: {}
- userData:
- assetBundleName:
- assetBundleVariant:
diff --git a/shaders/AdvancedErosionFilter.hlsl b/shaders/AdvancedErosionFilter.hlsl
deleted file mode 100644
index a0a350e..0000000
--- a/shaders/AdvancedErosionFilter.hlsl
+++ /dev/null
@@ -1,147 +0,0 @@
-// This Source Code Form is subject to the terms of the Mozilla Public
-// License, v. 2.0. If a copy of the MPL was not distributed with this file,
-// You can obtain one at https://mozilla.org/MPL/2.0/.
-// Mathematical procedure adapted from Rune Skovbo Johansen's Advanced Terrain
-// Erosion Filter (2025): https://www.shadertoy.com/view/wXcfWn
-
-#ifndef CULTMATH_ADVANCED_EROSION_FILTER_HLSL
-#define CULTMATH_ADVANCED_EROSION_FILTER_HLSL
-
-struct CultMathAdvancedErosionParameters
-{
- float scale; float strength; float gully_weight; float detail;
- float4 rounding; float4 onset; float2 assumed_slope;
- float cell_scale; float normalization; int octaves; float lacunarity; float gain;
-};
-
-struct CultMathAdvancedErosionResult
-{
- float3 delta; float magnitude; float ridge_map; float fade_target;
-};
-
-struct CultMathErosionBandSelection
-{
- int active_octaves; float final_octave_weight; float finest_included_wavelength; float unresolved_height_bound;
-};
-
-CultMathErosionBandSelection cultmath_select_erosion_bands(float base_wavelength, float sample_spacing, int maximum_octaves, float lacunarity, float base_amplitude, float gain, float transition_ratio)
-{
- base_wavelength=max(base_wavelength,1.0e-6); sample_spacing=max(sample_spacing,0.0); lacunarity=max(lacunarity,1.01); transition_ratio=max(transition_ratio,1.01); maximum_octaves=min(max(maximum_octaves,0),16);
- float nyquist=sample_spacing*2.0, wavelength=base_wavelength, final_weight=0.0, finest=0.0; int active=0;
- [loop] for(int octave=0; octave=active) omitted+=amplitude; else if(index==active-1) omitted+=amplitude*(1.0-final_weight); amplitude*=abs(gain);
- }
- CultMathErosionBandSelection result; result.active_octaves=active; result.final_octave_weight=active>0?final_weight:0.0; result.finest_included_wavelength=finest; result.unresolved_height_bound=omitted; return result;
-}
-
-float cultmath_erosion_hash_unit(int2 cell, uint salt)
-{
- uint value = (uint)cell.x * 0x8da6b343u ^ (uint)cell.y * 0xd8163841u ^ salt;
- value ^= value >> 16; value *= 0x7feb352du; value ^= value >> 15; value *= 0x846ca68bu; value ^= value >> 16;
- return (value & 0x00ffffffu) * (1.0 / 16777216.0);
-}
-float2 cultmath_erosion_hash2(float2 input)
-{
- int2 cell = (int2)input;
- return float2(cultmath_erosion_hash_unit(cell, 0x68bc21ebu), cultmath_erosion_hash_unit(cell, 0x02e5be93u)) * 2.0 - 1.0;
-}
-
-float4 cultmath_phacelle_noise(float2 position, float2 normal_direction, float frequency, float offset_cycles, float normalization)
-{
- float2 side_direction = float2(-normal_direction.y, normal_direction.x) * frequency * 6.28318530718;
- float offset = offset_cycles * 6.28318530718;
- float2 cell = floor(position);
- float2 local = frac(position);
- float2 phase = 0.0;
- float weight_sum = 0.0;
- [unroll] for (int x = -1; x <= 2; x++)
- [unroll] for (int y = -1; y <= 2; y++)
- {
- float2 grid_offset = float2(x, y);
- float2 grid_point = cell + grid_offset;
- float2 random_offset = cultmath_erosion_hash2(grid_point) * 0.5;
- float2 delta = local - grid_offset - random_offset;
- float weight = max(0.0, exp(-dot(delta, delta) * 2.0) - 0.01111);
- weight_sum += weight;
- float wave = dot(delta, side_direction) + offset;
- phase += float2(cos(wave), sin(wave)) * weight;
- }
- float2 interpolated = phase / max(weight_sum, 1.0e-20);
- float magnitude = max(1.0 - saturate(normalization), length(interpolated));
- return float4(interpolated / max(magnitude, 1.0e-20), side_direction);
-}
-
-float cultmath_erosion_pow_inverse(float value, float power) { return 1.0 - pow(1.0 - saturate(value), power); }
-float cultmath_erosion_ease_out(float value) { float inverse = 1.0 - saturate(value); return 1.0 - inverse * inverse; }
-float cultmath_erosion_smooth_start(float value, float smoothing)
-{
- return smoothing <= 1.0e-10 || value >= smoothing ? value - 0.5 * smoothing : 0.5 * value * value / smoothing;
-}
-float2 cultmath_erosion_safe_normalize(float2 value)
-{
- float value_length = length(value);
- return abs(value_length) > 1.0e-10 ? value / value_length : value;
-}
-
-CultMathAdvancedErosionResult cultmath_advanced_erosion_filter_banded(float2 position, float3 base_height_and_slope, float fade_target, CultMathAdvancedErosionParameters p, CultMathErosionBandSelection band)
-{
- float strength = p.strength * p.scale;
- fade_target = clamp(fade_target, -1.0, 1.0);
- float3 input_value = base_height_and_slope;
- float3 height_and_slope = input_value;
- float frequency = 1.0 / max(p.scale * p.cell_scale, 1.0e-10);
- float2 slope = height_and_slope.yz;
- float slope_length = max(length(slope), 1.0e-10);
- float magnitude = 0.0;
- float rounding_multiplier = 1.0;
- float input_rounding = lerp(p.rounding.y, p.rounding.x, saturate(fade_target + 0.5)) * p.rounding.z;
- float combined_mask = cultmath_erosion_ease_out(cultmath_erosion_smooth_start(slope_length * p.onset.x, input_rounding * p.onset.x));
- float ridge_mask = cultmath_erosion_ease_out(slope_length * p.onset.z);
- float ridge_fade = fade_target;
- float2 gully_slope = lerp(slope, slope / slope_length * p.assumed_slope.x, p.assumed_slope.y);
-
- int octave_count=min(min(max(p.octaves,0),8),max(band.active_octaves,0));
- [loop] for (int octave = 0; octave < octave_count; octave++)
- {
- float4 phacelle = cultmath_phacelle_noise(position * frequency, cultmath_erosion_safe_normalize(gully_slope), p.cell_scale, 0.25, p.normalization);
- float2 derivative_direction = phacelle.zw * -frequency;
- float sloping = abs(phacelle.y);
- gully_slope += sign(phacelle.y) * derivative_direction * strength * p.gully_weight;
- float3 gullies = float3(phacelle.x, phacelle.y * derivative_direction.x, phacelle.y * derivative_direction.y);
- float3 faded = lerp(float3(fade_target, 0.0, 0.0), gullies * p.gully_weight, combined_mask);
- float octave_weight=octave==octave_count-1?saturate(band.final_octave_weight):1.0;
- height_and_slope += faded * strength * octave_weight;
- magnitude += strength * octave_weight;
- fade_target = faded.x;
- float octave_rounding = lerp(p.rounding.y, p.rounding.x, saturate(phacelle.x + 0.5)) * rounding_multiplier;
- float new_mask = cultmath_erosion_ease_out(cultmath_erosion_smooth_start(sloping * p.onset.y, octave_rounding * p.onset.y));
- combined_mask = cultmath_erosion_pow_inverse(combined_mask, p.detail) * new_mask;
- ridge_fade = lerp(ridge_fade, lerp(ridge_fade, gullies.x, ridge_mask), octave_weight);
- ridge_mask *= lerp(1.0, cultmath_erosion_ease_out(sloping * p.onset.w), octave_weight);
- strength *= p.gain;
- frequency *= p.lacunarity;
- rounding_multiplier *= p.rounding.w;
- }
-
- CultMathAdvancedErosionResult result;
- result.delta = height_and_slope - input_value;
- result.magnitude = magnitude;
- result.ridge_map = ridge_fade * (1.0 - ridge_mask);
- result.fade_target = fade_target;
- return result;
-}
-
-CultMathAdvancedErosionResult cultmath_advanced_erosion_filter(float2 position, float3 base_height_and_slope, float fade_target, CultMathAdvancedErosionParameters p)
-{
- CultMathErosionBandSelection band; band.active_octaves=p.octaves; band.final_octave_weight=1.0; band.finest_included_wavelength=0.0; band.unresolved_height_bound=0.0;
- return cultmath_advanced_erosion_filter_banded(position, base_height_and_slope, fade_target, p, band);
-}
-
-#endif
diff --git a/shaders/AdvancedErosionFilter.hlsl.meta b/shaders/AdvancedErosionFilter.hlsl.meta
deleted file mode 100644
index a354e33..0000000
--- a/shaders/AdvancedErosionFilter.hlsl.meta
+++ /dev/null
@@ -1,7 +0,0 @@
-fileFormatVersion: 2
-guid: 3252ac02e8853ef4b9d1fbc5204b5c6b
-ShaderIncludeImporter:
- externalObjects: {}
- userData:
- assetBundleName:
- assetBundleVariant:
diff --git a/shaders/CultMath.hlsl b/shaders/CultMath.hlsl
index 84d244b..4c00fb6 100644
--- a/shaders/CultMath.hlsl
+++ b/shaders/CultMath.hlsl
@@ -299,98 +299,4 @@ float cultmath_value_noise_texture_bicubic(Texture2D noise_texture, Sampl
return cultmath_catmullrom(row0, row1, row2, row3, local.y);
}
-#include "AdvancedErosionFilter.hlsl"
-#include "Planetary.hlsl"
-
-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;
-}
-
-float3 cultmath_planetary_radial_refinement_step(
- float3 position,
- float3 ray_direction,
- float3 center,
- float target_radius,
- float minimum_derivative,
- float maximum_correction)
-{
- float3 local=position-center;
- float radius=length(local);
- float3 radial=local/max(radius,0.0001);
- float error=radius-target_radius;
- float derivative=dot(ray_direction,radial);
- float safe_minimum=max(minimum_derivative,0.0001);
- float safe_derivative=abs(derivative)>safe_minimum?derivative:(derivative<0?-safe_minimum:safe_minimum);
- float correction=clamp(-error/safe_derivative,-abs(maximum_correction),abs(maximum_correction));
- return position+ray_direction*correction;
-}
-
#endif
diff --git a/shaders/Planetary.hlsl b/shaders/Planetary.hlsl
deleted file mode 100644
index 81e312b..0000000
--- a/shaders/Planetary.hlsl
+++ /dev/null
@@ -1,392 +0,0 @@
-#ifndef CULTMATH_PLANETARY_HLSL
-#define CULTMATH_PLANETARY_HLSL
-
-static const float CULTMATH_PLANETARY_QUARTER_PI = 0.78539816339744830962;
-
-float3 cultmath_planetary_face_direction(int face, float2 coordinate)
-{
- float2 tangent = tan(coordinate * CULTMATH_PLANETARY_QUARTER_PI);
- float3 cube = face == 0 ? float3(1.0, tangent.y, -tangent.x)
- : face == 1 ? float3(-1.0, tangent.y, tangent.x)
- : face == 2 ? float3(tangent.x, 1.0, -tangent.y)
- : face == 3 ? float3(tangent.x, -1.0, tangent.y)
- : face == 4 ? float3(tangent.x, tangent.y, 1.0)
- : float3(-tangent.x, tangent.y, -1.0);
- return cultmath_normalize(cube);
-}
-
-float2 cultmath_planetary_face_coordinate(float3 direction, out int face)
-{
- float3 d = cultmath_normalize(direction);
- float3 a = abs(d);
- float2 cube_uv;
- if (a.x >= a.y && a.x >= a.z)
- {
- if (d.x >= 0.0) { face = 0; cube_uv = float2(-d.z, d.y) / a.x; }
- else { face = 1; cube_uv = float2(d.z, d.y) / a.x; }
- }
- else if (a.y >= a.z)
- {
- if (d.y >= 0.0) { face = 2; cube_uv = float2(d.x, -d.z) / a.y; }
- else { face = 3; cube_uv = float2(d.x, d.z) / a.y; }
- }
- else
- {
- if (d.z >= 0.0) { face = 4; cube_uv = d.xy / a.z; }
- else { face = 5; cube_uv = float2(-d.x, d.y) / a.z; }
- }
- return atan(cube_uv) / CULTMATH_PLANETARY_QUARTER_PI;
-}
-
-bool cultmath_planetary_page_local(float3 direction, float4 tile_address, float active, out float2 local)
-{
- int face;
- float2 uv = cultmath_planetary_face_coordinate(direction, face);
- local = 0.0;
- if (active < 0.5 || abs((float)face - tile_address.x) > 0.25) return false;
- float axis_tiles = exp2(tile_address.y);
- float2 scaled = (uv * 0.5 + 0.5) * axis_tiles;
- float2 canonical = floor(min(scaled, axis_tiles - 1.0e-5));
- if (any(abs(canonical - tile_address.zw) > 0.25)) return false;
- local = scaled - tile_address.zw;
- return all(local >= 0.0) && all(local <= 1.0);
-}
-
-float3 cultmath_planetary_surface_normal(float3 direction, float3 world_distance_tangent_gradient)
-{
- direction = cultmath_normalize(direction);
- float3 tangent_gradient = world_distance_tangent_gradient - direction * dot(world_distance_tangent_gradient, direction);
- return cultmath_normalize(direction - tangent_gradient);
-}
-
-float2 cultmath_planetary_equirectangular_forward(float3 direction)
-{
- direction = cultmath_normalize(direction);
- return float2(atan2(direction.y, direction.x) / CULTMATH_PI, asin(clamp(direction.z, -1.0, 1.0)) / CULTMATH_HALF_PI);
-}
-
-float3 cultmath_planetary_equirectangular_inverse(float2 coordinate)
-{
- float longitude = coordinate.x * CULTMATH_PI;
- float latitude = coordinate.y * CULTMATH_HALF_PI;
- float cos_latitude = cos(latitude);
- return cultmath_normalize(float3(cos_latitude * cos(longitude), cos_latitude * sin(longitude), sin(latitude)));
-}
-
-static const float CULTMATH_EQUAL_EARTH_A1 = 1.340264;
-static const float CULTMATH_EQUAL_EARTH_A2 = -0.081106;
-static const float CULTMATH_EQUAL_EARTH_A3 = 0.000893;
-static const float CULTMATH_EQUAL_EARTH_A4 = 0.003796;
-static const float CULTMATH_EQUAL_EARTH_M = 0.8660254037844386;
-static const float CULTMATH_EQUAL_EARTH_X_MAX = 2.706629;
-static const float CULTMATH_EQUAL_EARTH_Y_MAX = 1.3173628;
-
-float cultmath_equal_earth_y(float theta)
-{
- float theta2 = theta * theta;
- float theta6 = theta2 * theta2 * theta2;
- return theta * (CULTMATH_EQUAL_EARTH_A1 + CULTMATH_EQUAL_EARTH_A2 * theta2 + theta6 * (CULTMATH_EQUAL_EARTH_A3 + CULTMATH_EQUAL_EARTH_A4 * theta2));
-}
-
-float2 cultmath_planetary_equal_earth_forward(float3 direction)
-{
- direction = cultmath_normalize(direction);
- float longitude = atan2(direction.y, direction.x);
- float latitude = asin(clamp(direction.z, -1.0, 1.0));
- float theta = asin(CULTMATH_EQUAL_EARTH_M * sin(latitude));
- float theta2 = theta * theta;
- float theta6 = theta2 * theta2 * theta2;
- float denominator = CULTMATH_EQUAL_EARTH_M * (CULTMATH_EQUAL_EARTH_A1 + 3.0 * CULTMATH_EQUAL_EARTH_A2 * theta2 + theta6 * (7.0 * CULTMATH_EQUAL_EARTH_A3 + 9.0 * CULTMATH_EQUAL_EARTH_A4 * theta2));
- return float2(longitude * cos(theta) / denominator / CULTMATH_EQUAL_EARTH_X_MAX, cultmath_equal_earth_y(theta) / CULTMATH_EQUAL_EARTH_Y_MAX);
-}
-
-float3 cultmath_planetary_equal_earth_inverse(float2 coordinate)
-{
- float target_y = coordinate.y * CULTMATH_EQUAL_EARTH_Y_MAX;
- float theta = target_y / CULTMATH_EQUAL_EARTH_A1;
- [unroll] for (int iteration = 0; iteration < 8; iteration++)
- {
- float theta2 = theta * theta;
- float theta6 = theta2 * theta2 * theta2;
- float derivative = CULTMATH_EQUAL_EARTH_A1 + 3.0 * CULTMATH_EQUAL_EARTH_A2 * theta2 + theta6 * (7.0 * CULTMATH_EQUAL_EARTH_A3 + 9.0 * CULTMATH_EQUAL_EARTH_A4 * theta2);
- theta -= (cultmath_equal_earth_y(theta) - target_y) / derivative;
- }
- float latitude = asin(clamp(sin(theta) / CULTMATH_EQUAL_EARTH_M, -1.0, 1.0));
- float theta2 = theta * theta;
- float theta6 = theta2 * theta2 * theta2;
- float denominator = CULTMATH_EQUAL_EARTH_M * (CULTMATH_EQUAL_EARTH_A1 + 3.0 * CULTMATH_EQUAL_EARTH_A2 * theta2 + theta6 * (7.0 * CULTMATH_EQUAL_EARTH_A3 + 9.0 * CULTMATH_EQUAL_EARTH_A4 * theta2));
- float longitude = coordinate.x * CULTMATH_EQUAL_EARTH_X_MAX * denominator / cos(theta);
- float cos_latitude = cos(latitude);
- return cultmath_normalize(float3(cos_latitude * cos(longitude), cos_latitude * sin(longitude), sin(latitude)));
-}
-
-float cultmath_planetary_wrap_longitude(float longitude)
-{
- longitude = fmod(longitude, CULTMATH_TAU);
- if (longitude > CULTMATH_PI) longitude -= CULTMATH_TAU;
- if (longitude < -CULTMATH_PI) longitude += CULTMATH_TAU;
- return longitude;
-}
-
-float3 cultmath_planetary_lon_lat_direction(float longitude, float latitude)
-{
- float cos_latitude = cos(latitude);
- return cultmath_normalize(float3(cos_latitude * cos(longitude), cos_latitude * sin(longitude), sin(latitude)));
-}
-
-float cultmath_planetary_asinh(float value) { return log(value + sqrt(value * value + 1.0)); }
-float cultmath_planetary_sinh(float value) { return 0.5 * (exp(value) - exp(-value)); }
-
-bool cultmath_planetary_azimuthal_forward(float3 direction, float2 center_lon_lat, int kind, out float2 coordinate)
-{
- direction = cultmath_normalize(direction);
- float longitude = cultmath_planetary_wrap_longitude(atan2(direction.y, direction.x) - center_lon_lat.x);
- float latitude = asin(clamp(direction.z, -1.0, 1.0));
- float sin0 = sin(center_lon_lat.y), cos0 = cos(center_lon_lat.y);
- float sin_latitude = sin(latitude), cos_latitude = cos(latitude), cos_longitude = cos(longitude);
- float cos_c = clamp(sin0 * sin_latitude + cos0 * cos_latitude * cos_longitude, -1.0, 1.0);
- float2 raw = float2(cos_latitude * sin(longitude), cos0 * sin_latitude - sin0 * cos_latitude * cos_longitude);
- float factor = 1.0;
- if (kind == 3)
- {
- if (cos_c < 0.0) { coordinate = 0.0; return false; }
- }
- else if (kind == 4)
- {
- float c = acos(cos_c); factor = (c < 1.0e-6 ? 1.0 : c / sin(c)) / CULTMATH_PI;
- }
- else if (kind == 5) factor = sqrt(2.0 / max(1.0 + cos_c, 1.0e-20)) * 0.5;
- else
- {
- if (cos_c <= 0.0) { coordinate = 0.0; return false; }
- factor = 1.0 / cos_c;
- }
- coordinate = raw * factor;
- return true;
-}
-
-bool cultmath_planetary_azimuthal_inverse(float2 coordinate, float2 center_lon_lat, int kind, out float3 direction)
-{
- float rho = length(coordinate);
- float c;
- if (kind == 3) { if (rho > 1.0) { direction = 0.0; return false; } c = asin(clamp(rho, 0.0, 1.0)); }
- else if (kind == 4) { if (rho > 1.0) { direction = 0.0; return false; } c = rho * CULTMATH_PI; }
- else if (kind == 5) { if (rho > 1.0) { direction = 0.0; return false; } c = 2.0 * asin(clamp(rho, 0.0, 1.0)); }
- else c = atan(rho);
- if (rho < 1.0e-6) { direction = cultmath_planetary_lon_lat_direction(center_lon_lat.x, center_lon_lat.y); return true; }
- float sin_c = sin(c), cos_c = cos(c), sin0 = sin(center_lon_lat.y), cos0 = cos(center_lon_lat.y);
- float latitude = asin(clamp(cos_c * sin0 + coordinate.y * sin_c * cos0 / rho, -1.0, 1.0));
- float delta_longitude = atan2(coordinate.x * sin_c, rho * cos0 * cos_c - coordinate.y * sin0 * sin_c);
- direction = cultmath_planetary_lon_lat_direction(cultmath_planetary_wrap_longitude(delta_longitude + center_lon_lat.x), latitude);
- return true;
-}
-
-bool cultmath_planetary_cube_atlas_forward(float3 direction, out float2 coordinate)
-{
- int face;
- float2 face_coordinate = cultmath_planetary_face_coordinate(direction, face);
- int column = face % 3;
- int row = face / 3;
- coordinate = float2(-1.0 + (column + (face_coordinate.x + 1.0) * 0.5) * (2.0 / 3.0), 1.0 - (row + (face_coordinate.y + 1.0) * 0.5));
- return true;
-}
-
-bool cultmath_planetary_cube_atlas_inverse(float2 coordinate, out float3 direction)
-{
- if (any(coordinate < -1.0) || any(coordinate > 1.0)) { direction = 0.0; return false; }
- int column = min((int)((coordinate.x + 1.0) * 1.5), 2);
- int row = min((int)(1.0 - coordinate.y), 1);
- float u = ((coordinate.x + 1.0) * 1.5 - column) * 2.0 - 1.0;
- float v = ((1.0 - coordinate.y) - row) * 2.0 - 1.0;
- direction = cultmath_planetary_face_direction(row * 3 + column, float2(u, v));
- return true;
-}
-
-bool cultmath_planetary_projection_forward(float3 direction, int kind, float3 center_lon_lat_scale, out float2 coordinate)
-{
- float scale = max(center_lon_lat_scale.z, 1.0e-20);
- if (kind == 6)
- {
- bool valid = cultmath_planetary_cube_atlas_forward(direction, coordinate);
- coordinate /= scale; return valid;
- }
- if (kind >= 3)
- {
- bool valid = cultmath_planetary_azimuthal_forward(direction, center_lon_lat_scale.xy, kind, coordinate);
- coordinate /= scale; return valid;
- }
- direction = cultmath_normalize(direction);
- float longitude = cultmath_planetary_wrap_longitude(atan2(direction.y, direction.x) - center_lon_lat_scale.x);
- float latitude = asin(clamp(direction.z, -1.0, 1.0));
- if (kind == 0) coordinate = float2(longitude / CULTMATH_PI, latitude / CULTMATH_HALF_PI);
- else if (kind == 1)
- {
- const float maximum_latitude = 1.4844222297453324;
- if (abs(latitude) > maximum_latitude) { coordinate = 0.0; return false; }
- coordinate = float2(longitude / CULTMATH_PI, cultmath_planetary_asinh(tan(latitude)) / CULTMATH_PI);
- }
- else
- {
- float3 centered = cultmath_planetary_lon_lat_direction(longitude, latitude);
- coordinate = cultmath_planetary_equal_earth_forward(centered);
- }
- coordinate /= scale;
- return true;
-}
-
-bool cultmath_planetary_projection_inverse(float2 coordinate, int kind, float3 center_lon_lat_scale, out float3 direction)
-{
- coordinate *= max(center_lon_lat_scale.z, 1.0e-20);
- if (kind == 6) return cultmath_planetary_cube_atlas_inverse(coordinate, direction);
- if (kind >= 3) return cultmath_planetary_azimuthal_inverse(coordinate, center_lon_lat_scale.xy, kind, direction);
- if (any(abs(coordinate) > 1.0)) { direction = 0.0; return false; }
- float longitude, latitude;
- if (kind == 0) { longitude = coordinate.x * CULTMATH_PI; latitude = coordinate.y * CULTMATH_HALF_PI; }
- else if (kind == 1) { longitude = coordinate.x * CULTMATH_PI; latitude = atan(cultmath_planetary_sinh(coordinate.y * CULTMATH_PI)); }
- else
- {
- float3 centered = cultmath_planetary_equal_earth_inverse(coordinate);
- longitude = atan2(centered.y, centered.x); latitude = asin(clamp(centered.z, -1.0, 1.0));
- }
- direction = cultmath_planetary_lon_lat_direction(cultmath_planetary_wrap_longitude(longitude + center_lon_lat_scale.x), latitude);
- return true;
-}
-
-struct CultMathPlanetaryFieldDefinition
-{
- float radius;
- int seed;
- CultMathAdvancedErosionParameters erosion;
-};
-
-struct CultMathPlanetaryBaseFieldSample
-{
- float radial_displacement;
- float3 radial_gradient;
- float field_value;
- float3 field_gradient;
- float fade_target;
-};
-
-struct CultMathPlanetarySurfaceSample
-{
- float radial_displacement;
- float3 tangent_gradient;
- float3 surface_normal;
- float slope;
- float ridge;
- float gully;
- float finest_resolved_wavelength;
- float unresolved_height_bound;
-};
-
-struct CultMathPlanetaryPageSample
-{
- float4 height_gradient;
- float2 masks;
-};
-
-CultMathPlanetaryPageSample cultmath_planetary_page_lerp(
- CultMathPlanetaryPageSample a,
- CultMathPlanetaryPageSample b,
- CultMathPlanetaryPageSample c,
- CultMathPlanetaryPageSample d,
- float2 fraction)
-{
- CultMathPlanetaryPageSample result;
- result.height_gradient = lerp(lerp(a.height_gradient, b.height_gradient, fraction.x), lerp(c.height_gradient, d.height_gradient, fraction.x), fraction.y);
- result.masks = lerp(lerp(a.masks, b.masks, fraction.x), lerp(c.masks, d.masks, fraction.x), fraction.y);
- return result;
-}
-
-CultMathPlanetaryPageSample cultmath_planetary_residual_sample(
- CultMathPlanetarySurfaceSample child,
- CultMathPlanetarySurfaceSample parent,
- bool has_parent)
-{
- CultMathPlanetaryPageSample result;
- result.height_gradient = float4(child.radial_displacement, child.tangent_gradient);
- if (has_parent) result.height_gradient -= float4(parent.radial_displacement, parent.tangent_gradient);
- result.masks = float2(child.ridge, child.gully);
- return result;
-}
-
-float4 cultmath_planetary_summary_empty()
-{
- return float4(3.402823466e+38, -3.402823466e+38, 0.0, 0.0);
-}
-
-float4 cultmath_planetary_summary_accumulate(float4 summary, float4 height_gradient, float unresolved_height)
-{
- summary.x = min(summary.x, height_gradient.x);
- summary.y = max(summary.y, height_gradient.x);
- summary.z = max(summary.z, length(height_gradient.yzw));
- summary.w = max(summary.w, max(unresolved_height, 0.0));
- return summary;
-}
-
-float cultmath_planetary_pow4(float value) { float square = value * value; return square * square; }
-
-CultMathPlanetarySurfaceSample cultmath_planetary_field_sample(
- CultMathPlanetaryFieldDefinition definition,
- float3 unit_direction,
- CultMathPlanetaryBaseFieldSample base_sample,
- float footprint_meters)
-{
- float3 direction = cultmath_normalize(unit_direction);
- float3 radial_gradient = base_sample.radial_gradient - direction * dot(base_sample.radial_gradient, direction);
- float3 field_gradient = base_sample.field_gradient - direction * dot(base_sample.field_gradient, direction);
- CultMathAdvancedErosionParameters p = definition.erosion;
- CultMathErosionBandSelection band = cultmath_select_erosion_bands(
- p.scale * p.cell_scale, footprint_meters, p.octaves, p.lacunarity,
- p.strength * p.scale, p.gain, 2.0);
- float3 powers = float3(
- cultmath_planetary_pow4(abs(direction.x)),
- cultmath_planetary_pow4(abs(direction.y)),
- cultmath_planetary_pow4(abs(direction.z)));
- float power_sum = max(powers.x + powers.y + powers.z, 1.0e-6);
- float3 weights = powers / power_sum;
- float3 world = direction * definition.radius;
- float seed = (float)definition.seed;
- CultMathAdvancedErosionResult xy = cultmath_advanced_erosion_filter_banded(
- world.xy + float2(713.0 + seed * 0.754877666, -291.0 + seed * 0.569840296),
- float3(base_sample.field_value, field_gradient.x, field_gradient.y), base_sample.fade_target, p, band);
- CultMathAdvancedErosionResult yz = cultmath_advanced_erosion_filter_banded(
- world.yz + float2(-431.0 + seed * 0.438289027, 887.0 + seed * 0.328438163),
- float3(base_sample.field_value, field_gradient.y, field_gradient.z), base_sample.fade_target, p, band);
- CultMathAdvancedErosionResult zx = cultmath_advanced_erosion_filter_banded(
- world.zx + float2(197.0 + seed * 0.219783071, 557.0 + seed * 0.145898034),
- float3(base_sample.field_value, field_gradient.z, field_gradient.x), base_sample.fade_target, p, band);
- float erosion_height = xy.delta.x * weights.z + yz.delta.x * weights.x + zx.delta.x * weights.y;
- float3 erosion_gradient = float3(
- xy.delta.y * weights.z + zx.delta.z * weights.y,
- xy.delta.z * weights.z + yz.delta.y * weights.x,
- yz.delta.z * weights.x + zx.delta.y * weights.y);
- float3 power_gradient_x = float3(4.0 * direction.x * direction.x * direction.x / definition.radius, 0.0, 0.0);
- float3 power_gradient_y = float3(0.0, 4.0 * direction.y * direction.y * direction.y / definition.radius, 0.0);
- float3 power_gradient_z = float3(0.0, 0.0, 4.0 * direction.z * direction.z * direction.z / definition.radius);
- power_gradient_x -= direction * dot(power_gradient_x, direction);
- power_gradient_y -= direction * dot(power_gradient_y, direction);
- power_gradient_z -= direction * dot(power_gradient_z, direction);
- float3 power_sum_gradient = power_gradient_x + power_gradient_y + power_gradient_z;
- float inverse_power_sum_squared = 1.0 / (power_sum * power_sum);
- float3 weight_gradient_x = (power_gradient_x * power_sum - power_sum_gradient * powers.x) * inverse_power_sum_squared;
- float3 weight_gradient_y = (power_gradient_y * power_sum - power_sum_gradient * powers.y) * inverse_power_sum_squared;
- float3 weight_gradient_z = (power_gradient_z * power_sum - power_sum_gradient * powers.z) * inverse_power_sum_squared;
- erosion_gradient += yz.delta.x * weight_gradient_x + zx.delta.x * weight_gradient_y + xy.delta.x * weight_gradient_z;
- float3 gradient = radial_gradient + erosion_gradient;
- gradient -= direction * dot(gradient, direction);
- float ridge_evidence = xy.ridge_map * weights.z + yz.ridge_map * weights.x + zx.ridge_map * weights.y;
- CultMathPlanetarySurfaceSample result;
- result.radial_displacement = base_sample.radial_displacement + erosion_height;
- result.tangent_gradient = gradient;
- result.surface_normal = cultmath_planetary_surface_normal(direction, gradient);
- result.slope = length(gradient);
- result.ridge = saturate(ridge_evidence * 0.5 + 0.5);
- result.gully = saturate(0.5 - ridge_evidence * 0.5);
- result.finest_resolved_wavelength = band.finest_included_wavelength;
- result.unresolved_height_bound = band.unresolved_height_bound;
- return result;
-}
-
-#endif
diff --git a/shaders/Planetary.hlsl.meta b/shaders/Planetary.hlsl.meta
deleted file mode 100644
index f777135..0000000
--- a/shaders/Planetary.hlsl.meta
+++ /dev/null
@@ -1,7 +0,0 @@
-fileFormatVersion: 2
-guid: bb055ad5328888e479e90ae3fe62c3fe
-ShaderIncludeImporter:
- externalObjects: {}
- userData:
- assetBundleName:
- assetBundleVariant:
diff --git a/src/CultMath/AdvancedErosionFilter.cs b/src/CultMath/AdvancedErosionFilter.cs
deleted file mode 100644
index 65db367..0000000
--- a/src/CultMath/AdvancedErosionFilter.cs
+++ /dev/null
@@ -1,139 +0,0 @@
-// This Source Code Form is subject to the terms of the Mozilla Public
-// License, v. 2.0. If a copy of the MPL was not distributed with this file,
-// You can obtain one at https://mozilla.org/MPL/2.0/.
-//
-// Mathematical procedure adapted from Rune Skovbo Johansen's Advanced Terrain
-// Erosion Filter (2025): https://www.shadertoy.com/view/wXcfWn
-
-namespace CultMath;
-
-public readonly record struct AdvancedErosionParameters(
- float Scale,
- float Strength,
- float GullyWeight,
- float Detail,
- float4 Rounding,
- float4 Onset,
- float2 AssumedSlope,
- float CellScale,
- float Normalization,
- int Octaves,
- float Lacunarity,
- float Gain)
-{
- public static AdvancedErosionParameters Default => new(
- 0.15f, 0.22f, 0.5f, 1.5f,
- new float4(0.1f, 0.0f, 0.1f, 2.0f),
- new float4(1.25f, 1.25f, 2.8f, 1.5f),
- new float2(0.7f, 1.0f),
- 0.7f, 0.5f, 5, 2.0f, 0.5f);
-}
-
-public readonly record struct AdvancedErosionResult(float3 Delta, float Magnitude, float RidgeMap, float FadeTarget);
-
-public static class AdvancedErosionFilter
-{
- public static AdvancedErosionResult Sample(float2 position, float3 baseHeightAndSlope, float fadeTarget, AdvancedErosionParameters p)
- => Sample(position, baseHeightAndSlope, fadeTarget, p, new ErosionBandSelection(p.Octaves, 1.0f, 0.0f, 0.0f));
-
- public static AdvancedErosionResult Sample(float2 position, float3 baseHeightAndSlope, float fadeTarget, AdvancedErosionParameters p, ErosionBandSelection band)
- {
- var strength = p.Strength * p.Scale;
- fadeTarget = math.clamp(fadeTarget, -1.0f, 1.0f);
- var input = baseHeightAndSlope;
- var heightAndSlope = input;
- var frequency = 1.0f / math.max(p.Scale * p.CellScale, 1.0e-10f);
- var slope = new float2(heightAndSlope.y, heightAndSlope.z);
- var slopeLength = math.max(math.length(slope), 1.0e-10f);
- var magnitude = 0.0f;
- var roundingMultiplier = 1.0f;
-
- var inputRounding = math.lerp(p.Rounding.y, p.Rounding.x, math.saturate(fadeTarget + 0.5f)) * p.Rounding.z;
- var combinedMask = EaseOut(SmoothStart(slopeLength * p.Onset.x, inputRounding * p.Onset.x));
- var ridgeMask = EaseOut(slopeLength * p.Onset.z);
- var ridgeFade = fadeTarget;
- var gullySlope = math.lerp(slope, slope / slopeLength * p.AssumedSlope.x, p.AssumedSlope.y);
-
- var octaves = math.min(math.min(math.max(p.Octaves, 0), 8), math.max(band.ActiveOctaves, 0));
- for (var octave = 0; octave < octaves; octave++)
- {
- var phacelle = Phacelle(position * frequency, SafeNormalize(gullySlope), p.CellScale, 0.25f, p.Normalization);
- var derivativeDirection = new float2(phacelle.z, phacelle.w) * -frequency;
- var sloping = math.abs(phacelle.y);
- gullySlope += math.sign(phacelle.y) * derivativeDirection * strength * p.GullyWeight;
-
- var gullies = new float3(phacelle.x, phacelle.y * derivativeDirection.x, phacelle.y * derivativeDirection.y);
- var faded = math.lerp(new float3(fadeTarget, 0.0f, 0.0f), gullies * p.GullyWeight, combinedMask);
- var octaveWeight = octave == octaves - 1 ? math.saturate(band.FinalOctaveWeight) : 1.0f;
- heightAndSlope += faded * strength * octaveWeight;
- magnitude += strength * octaveWeight;
- fadeTarget = faded.x;
-
- var octaveRounding = math.lerp(p.Rounding.y, p.Rounding.x, math.saturate(phacelle.x + 0.5f)) * roundingMultiplier;
- var newMask = EaseOut(SmoothStart(sloping * p.Onset.y, octaveRounding * p.Onset.y));
- combinedMask = PowInverse(combinedMask, p.Detail) * newMask;
-
- ridgeFade = math.lerp(ridgeFade, math.lerp(ridgeFade, gullies.x, ridgeMask), octaveWeight);
- ridgeMask *= math.lerp(1.0f, EaseOut(sloping * p.Onset.w), octaveWeight);
- strength *= p.Gain;
- frequency *= p.Lacunarity;
- roundingMultiplier *= p.Rounding.w;
- }
-
- return new AdvancedErosionResult(heightAndSlope - input, magnitude, ridgeFade * (1.0f - ridgeMask), fadeTarget);
- }
-
- public static float4 Phacelle(float2 position, float2 normalDirection, float frequency, float offsetCycles, float normalization)
- {
- var sideDirection = new float2(-normalDirection.y, normalDirection.x) * frequency * math.TAU;
- var offset = offsetCycles * math.TAU;
- var cell = math.floor(position);
- var local = math.frac(position);
- var phase = float2.zero;
- var weightSum = 0.0f;
-
- for (var x = -1; x <= 2; x++)
- for (var y = -1; y <= 2; y++)
- {
- var gridOffset = new float2(x, y);
- var gridPoint = cell + gridOffset;
- var randomOffset = Hash2(gridPoint) * 0.5f;
- var delta = local - gridOffset - randomOffset;
- var weight = math.max(0.0f, math.exp(-math.dot(delta, delta) * 2.0f) - 0.01111f);
- weightSum += weight;
- var wave = math.dot(delta, sideDirection) + offset;
- phase += new float2(math.cos(wave), math.sin(wave)) * weight;
- }
-
- var interpolated = phase / math.max(weightSum, 1.0e-20f);
- var magnitude = math.max(1.0f - math.saturate(normalization), math.length(interpolated));
- var normalized = interpolated / math.max(magnitude, 1.0e-20f);
- return new float4(normalized, sideDirection.x, sideDirection.y);
- }
-
- private static float2 Hash2(float2 input)
- {
- var x = (int)input.x;
- var y = (int)input.y;
- return new float2(HashUnit(x, y, 0x68bc21ebu), HashUnit(x, y, 0x02e5be93u)) * 2.0f - float2.one;
- }
-
- private static float HashUnit(int x, int y, uint salt)
- {
- var value = unchecked((uint)x * 0x8da6b343u ^ (uint)y * 0xd8163841u ^ salt);
- value ^= value >> 16; value *= 0x7feb352du; value ^= value >> 15; value *= 0x846ca68bu; value ^= value >> 16;
- return (value & 0x00ffffffu) * (1.0f / 16777216.0f);
- }
-
- private static float2 SafeNormalize(float2 value)
- {
- var length = math.length(value);
- return math.abs(length) > 1.0e-10f ? value / length : value;
- }
-
- private static float PowInverse(float value, float power) => 1.0f - math.pow(1.0f - math.saturate(value), power);
- private static float EaseOut(float value) { var inverse = 1.0f - math.saturate(value); return 1.0f - inverse * inverse; }
- private static float SmoothStart(float value, float smoothing) => smoothing <= 1.0e-10f || value >= smoothing
- ? value - 0.5f * smoothing
- : 0.5f * value * value / smoothing;
-}
diff --git a/src/CultMath/AdvancedErosionFilter.cs.meta b/src/CultMath/AdvancedErosionFilter.cs.meta
deleted file mode 100644
index 52f7ca1..0000000
--- a/src/CultMath/AdvancedErosionFilter.cs.meta
+++ /dev/null
@@ -1,2 +0,0 @@
-fileFormatVersion: 2
-guid: bec15fdcbe97a7f44a24bdb3ff79e1e7
\ No newline at end of file
diff --git a/src/CultMath/CultMath.csproj b/src/CultMath/CultMath.csproj
index 61b57c9..01901b6 100644
--- a/src/CultMath/CultMath.csproj
+++ b/src/CultMath/CultMath.csproj
@@ -19,8 +19,6 @@
-
-
diff --git a/src/CultMath/ErosionFrequencyBands.cs b/src/CultMath/ErosionFrequencyBands.cs
deleted file mode 100644
index 3c63eb6..0000000
--- a/src/CultMath/ErosionFrequencyBands.cs
+++ /dev/null
@@ -1,57 +0,0 @@
-namespace CultMath;
-
-public readonly record struct ErosionBandSelection(
- int ActiveOctaves,
- float FinalOctaveWeight,
- float FinestIncludedWavelength,
- float UnresolvedHeightBound)
-{
- public bool HasSamples => ActiveOctaves > 0;
-}
-
-public static class ErosionFrequencyBands
-{
- public static ErosionBandSelection Select(
- float baseWavelength,
- float sampleSpacing,
- int maximumOctaves,
- float lacunarity,
- float baseAmplitude,
- float gain,
- float transitionRatio = 2.0f)
- {
- baseWavelength = math.max(baseWavelength, 1.0e-6f);
- sampleSpacing = math.max(sampleSpacing, 0.0f);
- lacunarity = math.max(lacunarity, 1.01f);
- transitionRatio = math.max(transitionRatio, 1.01f);
- maximumOctaves = math.min(math.max(maximumOctaves, 0), 16);
- var nyquistWavelength = sampleSpacing * 2.0f;
- var wavelength = baseWavelength;
- var active = 0;
- var finalWeight = 0.0f;
- var finest = 0.0f;
-
- for (var octave = 0; octave < maximumOctaves; octave++)
- {
- var weight = nyquistWavelength <= 0.0f
- ? 1.0f
- : math.smoothstep(nyquistWavelength, nyquistWavelength * transitionRatio, wavelength);
- if (weight <= 0.0f) break;
- active++;
- finalWeight = weight;
- finest = wavelength;
- wavelength /= lacunarity;
- }
-
- var omittedAmplitude = 0.0f;
- var amplitude = math.abs(baseAmplitude);
- for (var octave = 0; octave < maximumOctaves; octave++)
- {
- if (octave >= active) omittedAmplitude += amplitude;
- else if (octave == active - 1) omittedAmplitude += amplitude * (1.0f - finalWeight);
- amplitude *= math.abs(gain);
- }
-
- return new ErosionBandSelection(active, active > 0 ? finalWeight : 0.0f, finest, omittedAmplitude);
- }
-}
diff --git a/src/CultMath/ErosionFrequencyBands.cs.meta b/src/CultMath/ErosionFrequencyBands.cs.meta
deleted file mode 100644
index cae9964..0000000
--- a/src/CultMath/ErosionFrequencyBands.cs.meta
+++ /dev/null
@@ -1,2 +0,0 @@
-fileFormatVersion: 2
-guid: 05e7969589dd09b43bd0fda672d25a0b
\ No newline at end of file
diff --git a/src/CultMath/PlanetaryField.cs b/src/CultMath/PlanetaryField.cs
deleted file mode 100644
index 4d5596e..0000000
--- a/src/CultMath/PlanetaryField.cs
+++ /dev/null
@@ -1,215 +0,0 @@
-using System;
-
-namespace CultMath;
-
-public readonly record struct PlanetaryQueryScale(float FootprintMeters, float MaximumUnresolvedHeight)
-{
- public static PlanetaryQueryScale AtFootprint(float footprintMeters, float maximumUnresolvedHeight = float.PositiveInfinity)
- => new PlanetaryQueryScale(footprintMeters, maximumUnresolvedHeight).Validate();
-
- public PlanetaryQueryScale Validate()
- {
- if (!float.IsFinite(FootprintMeters) || FootprintMeters < 0) throw new ArgumentOutOfRangeException(nameof(FootprintMeters));
- if (float.IsNaN(MaximumUnresolvedHeight) || MaximumUnresolvedHeight < 0) throw new ArgumentOutOfRangeException(nameof(MaximumUnresolvedHeight));
- return this;
- }
-}
-
-public readonly record struct PlanetaryFieldDefinition(
- ulong FieldVersion,
- float Radius,
- int Seed,
- AdvancedErosionParameters Erosion)
-{
- public static PlanetaryFieldDefinition Create(
- ulong baseFieldVersion,
- float radius,
- int seed,
- AdvancedErosionParameters erosion)
- => new PlanetaryFieldDefinition(PlanetaryFieldIdentity.Compute(baseFieldVersion, radius, seed, erosion), radius, seed, erosion).Validate();
-
- public PlanetaryFieldDefinition Validate()
- {
- if (FieldVersion == 0) throw new ArgumentOutOfRangeException(nameof(FieldVersion));
- if (!float.IsFinite(Radius) || Radius <= 0) throw new ArgumentOutOfRangeException(nameof(Radius));
- if (!float.IsFinite(Erosion.Scale) || Erosion.Scale <= 0) throw new ArgumentOutOfRangeException(nameof(Erosion));
- return this;
- }
-}
-
-public static class PlanetaryFieldIdentity
-{
- public static ulong Compute(ulong baseFieldVersion, float radius, int seed, in AdvancedErosionParameters erosion)
- {
- if (baseFieldVersion == 0) throw new ArgumentOutOfRangeException(nameof(baseFieldVersion));
- const ulong offset = 14695981039346656037UL;
- const ulong prime = 1099511628211UL;
- var hash = offset;
- Add(baseFieldVersion);
- Add((uint)BitConverter.SingleToInt32Bits(radius)); Add((uint)seed);
- Add((uint)BitConverter.SingleToInt32Bits(erosion.Scale)); Add((uint)BitConverter.SingleToInt32Bits(erosion.Strength));
- Add((uint)BitConverter.SingleToInt32Bits(erosion.GullyWeight)); Add((uint)BitConverter.SingleToInt32Bits(erosion.Detail));
- AddFloat4(erosion.Rounding); AddFloat4(erosion.Onset);
- Add((uint)BitConverter.SingleToInt32Bits(erosion.AssumedSlope.x)); Add((uint)BitConverter.SingleToInt32Bits(erosion.AssumedSlope.y));
- Add((uint)BitConverter.SingleToInt32Bits(erosion.CellScale)); Add((uint)BitConverter.SingleToInt32Bits(erosion.Normalization));
- Add((uint)erosion.Octaves); Add((uint)BitConverter.SingleToInt32Bits(erosion.Lacunarity)); Add((uint)BitConverter.SingleToInt32Bits(erosion.Gain));
- return hash == 0 ? 1 : hash;
-
- void Add(ulong value) => hash = unchecked((hash ^ value) * prime);
- void AddFloat4(float4 value)
- {
- Add((uint)BitConverter.SingleToInt32Bits(value.x)); Add((uint)BitConverter.SingleToInt32Bits(value.y));
- Add((uint)BitConverter.SingleToInt32Bits(value.z)); Add((uint)BitConverter.SingleToInt32Bits(value.w));
- }
- }
-}
-
-///
-/// Application-supplied base evidence at one unit spherical direction.
-/// FieldValue and FieldGradient drive erosion. RadialDisplacement and
-/// RadialGradient describe the uneroded surface in metres.
-///
-public readonly record struct PlanetaryBaseFieldSample(
- float RadialDisplacement,
- float3 RadialGradient,
- float FieldValue,
- float3 FieldGradient,
- float FadeTarget);
-
-public readonly record struct PlanetarySurfaceSample(
- ulong FieldVersion,
- float3 UnitDirection,
- float Radius,
- float RadialDisplacement,
- float3 TangentGradient,
- float3 SurfaceNormal,
- float Slope,
- float Ridge,
- float Gully,
- float FinestResolvedWavelength,
- float UnresolvedHeightBound)
-{
- public float SurfaceRadius => Radius + RadialDisplacement;
- public float3 PlanetLocalPosition => UnitDirection * SurfaceRadius;
- public bool Meets(PlanetaryQueryScale scale) => UnresolvedHeightBound <= scale.MaximumUnresolvedHeight;
-}
-
-public static class PlanetaryField
-{
- public static PlanetarySurfaceSample Sample(
- in PlanetaryFieldDefinition definition,
- float3 unitDirection,
- in PlanetaryBaseFieldSample baseSample,
- in PlanetaryQueryScale scale)
- {
- definition.Validate();
- scale.Validate();
- PlanetaryTopology.ValidateDirection(unitDirection);
- PlanetaryTopology.ValidateFinite(baseSample.RadialGradient, nameof(baseSample));
- PlanetaryTopology.ValidateFinite(baseSample.FieldGradient, nameof(baseSample));
- if (!float.IsFinite(baseSample.RadialDisplacement) || !float.IsFinite(baseSample.FieldValue) || !float.IsFinite(baseSample.FadeTarget))
- throw new ArgumentOutOfRangeException(nameof(baseSample));
-
- var direction = math.normalize(unitDirection);
- var radialGradient = Tangent(direction, baseSample.RadialGradient);
- var fieldGradient = Tangent(direction, baseSample.FieldGradient);
- var p = definition.Erosion;
- var band = ErosionFrequencyBands.Select(
- p.Scale * p.CellScale,
- scale.FootprintMeters,
- p.Octaves,
- p.Lacunarity,
- p.Strength * p.Scale,
- p.Gain);
-
- var powers = new float3(
- Pow4(MathF.Abs(direction.x)),
- Pow4(MathF.Abs(direction.y)),
- Pow4(MathF.Abs(direction.z)));
- var powerSum = math.max(powers.x + powers.y + powers.z, 1.0e-6f);
- var weights = powers / powerSum;
- var world = direction * definition.Radius;
- var seed = definition.Seed;
- var xy = AdvancedErosionFilter.Sample(
- world.xy + new float2(713 + seed * 0.754877666f, -291 + seed * 0.569840296f),
- new float3(baseSample.FieldValue, fieldGradient.x, fieldGradient.y), baseSample.FadeTarget, p, band);
- var yz = AdvancedErosionFilter.Sample(
- world.yz + new float2(-431 + seed * 0.438289027f, 887 + seed * 0.328438163f),
- new float3(baseSample.FieldValue, fieldGradient.y, fieldGradient.z), baseSample.FadeTarget, p, band);
- var zx = AdvancedErosionFilter.Sample(
- new float2(world.z, world.x) + new float2(197 + seed * 0.219783071f, 557 + seed * 0.145898034f),
- new float3(baseSample.FieldValue, fieldGradient.z, fieldGradient.x), baseSample.FadeTarget, p, band);
-
- var erosionHeight = xy.Delta.x * weights.z + yz.Delta.x * weights.x + zx.Delta.x * weights.y;
- var erosionGradient = new float3(
- xy.Delta.y * weights.z + zx.Delta.z * weights.y,
- xy.Delta.z * weights.z + yz.Delta.y * weights.x,
- yz.Delta.z * weights.x + zx.Delta.y * weights.y);
- var powerGradientX = Tangent(direction, new float3(4 * direction.x * direction.x * direction.x / definition.Radius, 0, 0));
- var powerGradientY = Tangent(direction, new float3(0, 4 * direction.y * direction.y * direction.y / definition.Radius, 0));
- var powerGradientZ = Tangent(direction, new float3(0, 0, 4 * direction.z * direction.z * direction.z / definition.Radius));
- var sumGradient = powerGradientX + powerGradientY + powerGradientZ;
- var weightGradientX = (powerGradientX * powerSum - sumGradient * powers.x) / (powerSum * powerSum);
- var weightGradientY = (powerGradientY * powerSum - sumGradient * powers.y) / (powerSum * powerSum);
- var weightGradientZ = (powerGradientZ * powerSum - sumGradient * powers.z) / (powerSum * powerSum);
- erosionGradient += yz.Delta.x * weightGradientX + zx.Delta.x * weightGradientY + xy.Delta.x * weightGradientZ;
- var gradient = Tangent(direction, radialGradient + erosionGradient);
- var ridgeEvidence = xy.RidgeMap * weights.z + yz.RidgeMap * weights.x + zx.RidgeMap * weights.y;
- var ridge = math.saturate(ridgeEvidence * 0.5f + 0.5f);
- var gully = math.saturate(0.5f - ridgeEvidence * 0.5f);
- var displacement = baseSample.RadialDisplacement + erosionHeight;
-
- return new(
- definition.FieldVersion,
- direction,
- definition.Radius,
- displacement,
- gradient,
- PlanetaryTopology.SurfaceNormal(direction, gradient),
- math.length(gradient),
- ridge,
- gully,
- band.FinestIncludedWavelength,
- band.UnresolvedHeightBound);
- }
-
- public static void SampleBatch(
- in PlanetaryFieldDefinition definition,
- ReadOnlySpan directions,
- in PlanetaryQueryScale scale,
- TSource source,
- Span destination)
- where TSource : IPlanetaryBaseField
- {
- if (destination.Length < directions.Length) throw new ArgumentException("Destination is shorter than directions.", nameof(destination));
- for (var i = 0; i < directions.Length; i++)
- {
- var direction = math.normalize(directions[i]);
- var baseSample = source.Sample(direction);
- destination[i] = Sample(definition, direction, baseSample, scale);
- }
- }
-
- public static PlanetarySurfaceSample SamplePosition(
- in PlanetaryFieldDefinition definition,
- double3 planetLocalPosition,
- in PlanetaryQueryScale scale,
- TSource source)
- where TSource : IPlanetaryBaseField
- {
- if (!double.IsFinite(planetLocalPosition.x) || !double.IsFinite(planetLocalPosition.y) || !double.IsFinite(planetLocalPosition.z))
- throw new ArgumentOutOfRangeException(nameof(planetLocalPosition));
- var length = Math.Sqrt(planetLocalPosition.x * planetLocalPosition.x + planetLocalPosition.y * planetLocalPosition.y + planetLocalPosition.z * planetLocalPosition.z);
- if (length < 1.0e-12) throw new ArgumentOutOfRangeException(nameof(planetLocalPosition));
- var direction = new float3((float)(planetLocalPosition.x / length), (float)(planetLocalPosition.y / length), (float)(planetLocalPosition.z / length));
- return Sample(definition, direction, source.Sample(direction), scale);
- }
-
- private static float3 Tangent(float3 direction, float3 gradient) => gradient - direction * math.dot(gradient, direction);
- private static float Pow4(float value) { var square = value * value; return square * square; }
-}
-
-public interface IPlanetaryBaseField
-{
- PlanetaryBaseFieldSample Sample(float3 unitDirection);
-}
diff --git a/src/CultMath/PlanetaryField.cs.meta b/src/CultMath/PlanetaryField.cs.meta
deleted file mode 100644
index 3e83f63..0000000
--- a/src/CultMath/PlanetaryField.cs.meta
+++ /dev/null
@@ -1,2 +0,0 @@
-fileFormatVersion: 2
-guid: b61d6354e3aab054e94721e77e619bfa
\ No newline at end of file
diff --git a/src/CultMath/PlanetaryGpuPages.cs b/src/CultMath/PlanetaryGpuPages.cs
deleted file mode 100644
index 024d26b..0000000
--- a/src/CultMath/PlanetaryGpuPages.cs
+++ /dev/null
@@ -1,55 +0,0 @@
-using System;
-
-namespace CultMath;
-
-public readonly record struct PlanetaryGpuPageInput(float4 DirectionRadius, float4 Sampling);
-
-public readonly record struct PlanetaryGpuPageMetadata(float4 Address, float4 Layout, float4 Bounds, float4 State);
-
-public readonly record struct PlanetaryGpuPageContent(
- PlanetaryPageLayout Layout,
- PlanetaryGpuPageInput[] Inputs,
- float SampleSpacing,
- float ParentSampleSpacing);
-
-public static class PlanetaryGpuPageBuilder
-{
- public static PlanetaryGpuPageContent BuildContent(PlanetaryPageLayout layout, float radius)
- {
- layout.Validate();
- if (!float.IsFinite(radius) || radius <= 0) throw new ArgumentOutOfRangeException(nameof(radius));
- var spacing = PlanetaryPageSampling.NominalAngularTexelSize(layout) * radius;
- var parentSpacing = layout.Tile.Level == 0
- ? 0
- : PlanetaryPageSampling.NominalAngularTexelSize(new(layout.Tile.Parent(), layout.InteriorSize, layout.BorderSize)) * radius;
- var inputs = new PlanetaryGpuPageInput[layout.StorageSize * layout.StorageSize];
- for (var y = 0; y < layout.StorageSize; y++)
- for (var x = 0; x < layout.StorageSize; x++)
- {
- var direction = PlanetaryPageSampling.Direction(layout, x, y);
- inputs[y * layout.StorageSize + x] = new(
- new float4(direction, radius),
- new float4(spacing, parentSpacing, 0, 0));
- }
- return new(layout, inputs, spacing, parentSpacing);
- }
-
- public static PlanetaryGpuPageMetadata Metadata(
- in PlanetaryGpuPageContent content,
- int outputOffset,
- float blend,
- float4 bounds = default)
- {
- content.Layout.Validate();
- if (content.Inputs == null || content.Inputs.Length != content.Layout.StorageSize * content.Layout.StorageSize)
- throw new ArgumentException("GPU page content does not match its layout.", nameof(content));
- if (outputOffset < 0) throw new ArgumentOutOfRangeException(nameof(outputOffset));
- if (!float.IsFinite(blend) || blend is < 0 or > 1) throw new ArgumentOutOfRangeException(nameof(blend));
- var tile = content.Layout.Tile;
- return new(
- new float4((int)tile.Face, tile.Level, tile.X, tile.Y),
- new float4(outputOffset, content.Layout.StorageSize, content.Layout.InteriorSize, content.Layout.BorderSize),
- bounds,
- new float4(1, blend, PlanetaryPageSampling.NominalAngularTexelSize(content.Layout), content.SampleSpacing));
- }
-}
diff --git a/src/CultMath/PlanetaryGpuPages.cs.meta b/src/CultMath/PlanetaryGpuPages.cs.meta
deleted file mode 100644
index 7afe847..0000000
--- a/src/CultMath/PlanetaryGpuPages.cs.meta
+++ /dev/null
@@ -1,2 +0,0 @@
-fileFormatVersion: 2
-guid: 42173c60766948a4e8c376687033532e
\ No newline at end of file
diff --git a/src/CultMath/PlanetaryMapTileEncoding.cs b/src/CultMath/PlanetaryMapTileEncoding.cs
deleted file mode 100644
index ec55661..0000000
--- a/src/CultMath/PlanetaryMapTileEncoding.cs
+++ /dev/null
@@ -1,117 +0,0 @@
-using System;
-using System.IO;
-using System.Text;
-
-namespace CultMath;
-
-public static class PlanetaryMapTileEncoding
-{
- public const uint FormatVersion = 1;
- private static readonly byte[] Magic = Encoding.ASCII.GetBytes("CMPT");
-
- public static void Write(Stream stream, in PlanetarySurfaceMapTile tile, bool leaveOpen = false)
- {
- if (stream == null || !stream.CanWrite) throw new ArgumentException("A writable stream is required.", nameof(stream));
- tile.Key.Validate();
- if (tile.Samples == null || tile.Validity == null || tile.Samples.Length != tile.Validity.Length)
- throw new ArgumentException("Tile samples and validity must have equal lengths.", nameof(tile));
- var expectedCount = tile.Key.Layout.StorageSize * tile.Key.Layout.StorageSize;
- if (tile.Samples.Length != expectedCount) throw new ArgumentException("Tile payload does not match its layout.", nameof(tile));
-
- using var writer = new BinaryWriter(stream, Encoding.UTF8, leaveOpen);
- writer.Write(Magic);
- writer.Write(FormatVersion);
- writer.Write(tile.Key.FieldVersion);
- writer.Write(tile.Key.ProjectionVersion);
- writer.Write(tile.Key.LayerId);
- var projection = tile.Key.Layout.Projection;
- writer.Write((int)projection.Kind);
- writer.Write(projection.CenterLongitude);
- writer.Write(projection.CenterLatitude);
- writer.Write(projection.Scale);
- writer.Write(tile.Key.Layout.Level);
- writer.Write(tile.Key.Layout.X);
- writer.Write(tile.Key.Layout.Y);
- writer.Write(tile.Key.Layout.InteriorSize);
- writer.Write(tile.Key.Layout.BorderSize);
- writer.Write(tile.Key.QueryScale.FootprintMeters);
- writer.Write(tile.Key.QueryScale.MaximumUnresolvedHeight);
- writer.Write(tile.Samples.Length);
- for (var i = 0; i < tile.Samples.Length; i++)
- {
- writer.Write(tile.Validity[i]);
- if (!tile.Validity[i]) continue;
- WriteSample(writer, tile.Samples[i], tile.Key.FieldVersion);
- }
- }
-
- public static PlanetarySurfaceMapTile Read(Stream stream, bool leaveOpen = false)
- {
- if (stream == null || !stream.CanRead) throw new ArgumentException("A readable stream is required.", nameof(stream));
- using var reader = new BinaryReader(stream, Encoding.UTF8, leaveOpen);
- var magic = reader.ReadBytes(Magic.Length);
- if (magic.Length != Magic.Length || !magic.AsSpan().SequenceEqual(Magic)) throw new InvalidDataException("Not a CultMath planetary tile.");
- var version = reader.ReadUInt32();
- if (version != FormatVersion) throw new InvalidDataException($"Unsupported planetary tile version {version}.");
- var fieldVersion = reader.ReadUInt64();
- var projectionVersion = reader.ReadUInt32();
- var layerId = reader.ReadUInt32();
- var kindValue = reader.ReadInt32();
- if (!Enum.IsDefined(typeof(PlanetaryProjectionKind), kindValue)) throw new InvalidDataException("Unknown planetary projection kind.");
- var projection = new PlanetaryProjectionParameters(
- (PlanetaryProjectionKind)kindValue,
- reader.ReadDouble(), reader.ReadDouble(), reader.ReadDouble()).Validate();
- var layout = new PlanetaryMapTileLayout(
- projection,
- reader.ReadInt32(), reader.ReadInt32(), reader.ReadInt32(),
- reader.ReadInt32(), reader.ReadInt32()).Validate();
- var scale = new PlanetaryQueryScale(reader.ReadSingle(), reader.ReadSingle()).Validate();
- var key = new PlanetaryMapTileKey(fieldVersion, projectionVersion, layerId, layout, scale).Validate();
- var count = reader.ReadInt32();
- if (count != layout.StorageSize * layout.StorageSize) throw new InvalidDataException("Planetary tile sample count does not match layout.");
- var samples = new PlanetarySurfaceSample[count];
- var validity = new bool[count];
- for (var i = 0; i < count; i++)
- {
- validity[i] = reader.ReadBoolean();
- if (validity[i]) samples[i] = ReadSample(reader, fieldVersion);
- }
- if (stream.CanSeek && stream.Position != stream.Length) throw new InvalidDataException("Planetary tile has trailing bytes.");
- return new(key, samples, validity);
- }
-
- private static void WriteSample(BinaryWriter writer, PlanetarySurfaceSample sample, ulong fieldVersion)
- {
- if (sample.FieldVersion != fieldVersion) throw new ArgumentException("Sample field version does not match tile identity.");
- Write(writer, sample.UnitDirection);
- writer.Write(sample.Radius);
- writer.Write(sample.RadialDisplacement);
- Write(writer, sample.TangentGradient);
- Write(writer, sample.SurfaceNormal);
- writer.Write(sample.Slope);
- writer.Write(sample.Ridge);
- writer.Write(sample.Gully);
- writer.Write(sample.FinestResolvedWavelength);
- writer.Write(sample.UnresolvedHeightBound);
- }
-
- private static PlanetarySurfaceSample ReadSample(BinaryReader reader, ulong fieldVersion)
- {
- var direction = ReadFloat3(reader);
- var radius = reader.ReadSingle();
- var displacement = reader.ReadSingle();
- var gradient = ReadFloat3(reader);
- var normal = ReadFloat3(reader);
- return new(
- fieldVersion, direction, radius, displacement, gradient, normal,
- reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle(),
- reader.ReadSingle(), reader.ReadSingle());
- }
-
- private static void Write(BinaryWriter writer, float3 value)
- {
- writer.Write(value.x); writer.Write(value.y); writer.Write(value.z);
- }
-
- private static float3 ReadFloat3(BinaryReader reader) => new(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle());
-}
diff --git a/src/CultMath/PlanetaryMapTileEncoding.cs.meta b/src/CultMath/PlanetaryMapTileEncoding.cs.meta
deleted file mode 100644
index 76484f6..0000000
--- a/src/CultMath/PlanetaryMapTileEncoding.cs.meta
+++ /dev/null
@@ -1,2 +0,0 @@
-fileFormatVersion: 2
-guid: 6898f6de57df20646a0b9077bb923592
\ No newline at end of file
diff --git a/src/CultMath/PlanetaryPages.cs b/src/CultMath/PlanetaryPages.cs
deleted file mode 100644
index 699a5c1..0000000
--- a/src/CultMath/PlanetaryPages.cs
+++ /dev/null
@@ -1,187 +0,0 @@
-using System;
-
-namespace CultMath;
-
-public readonly record struct PlanetaryPageLayout(PlanetaryTileAddress Tile, int InteriorSize, int BorderSize)
-{
- public int StorageSize => checked(InteriorSize + BorderSize * 2);
-
- public PlanetaryPageLayout Validate()
- {
- if (InteriorSize < 2) throw new ArgumentOutOfRangeException(nameof(InteriorSize));
- if (BorderSize < 0 || BorderSize >= InteriorSize) throw new ArgumentOutOfRangeException(nameof(BorderSize));
- return this;
- }
-}
-
-public readonly record struct PlanetaryPageSummary(
- float MinimumHeight,
- float MaximumHeight,
- float MaximumSlope,
- float UnresolvedHeightBound,
- float AngularTexelSize);
-
-public readonly record struct PlanetaryPageSample(float Height, float3 TangentGradient, float Ridge, float Gully);
-
-public static class PlanetaryPageSampling
-{
- public static float3 Direction(PlanetaryPageLayout page, int storageX, int storageY)
- {
- page.Validate();
- if ((uint)storageX >= (uint)page.StorageSize || (uint)storageY >= (uint)page.StorageSize)
- throw new ArgumentOutOfRangeException(nameof(storageX));
- var localU = (storageX - page.BorderSize) / (double)(page.InteriorSize - 1);
- var localV = (storageY - page.BorderSize) / (double)(page.InteriorSize - 1);
- return DirectionAtLocal(page, localU, localV);
- }
-
- public static float3 DirectionAtLocal(PlanetaryPageLayout page, double localU, double localV)
- {
- page.Validate();
- if (!double.IsFinite(localU) || !double.IsFinite(localV)) throw new ArgumentOutOfRangeException(nameof(localU));
- var count = page.Tile.AxisTileCount;
- return PlanetaryTopology.Direction(new(
- page.Tile.Face,
- -1.0 + 2.0 * (page.Tile.X + localU) / count,
- -1.0 + 2.0 * (page.Tile.Y + localV) / count));
- }
-
- public static float AngularTexelSize(PlanetaryPageLayout page)
- {
- var center = page.BorderSize + (page.InteriorSize - 1) / 2;
- var a = Direction(page, center, center);
- var b = Direction(page, Math.Min(center + 1, page.StorageSize - 1), center);
- return MathF.Acos(math.clamp(math.dot(a, b), -1.0f, 1.0f));
- }
-
- public static float NominalAngularTexelSize(PlanetaryPageLayout page)
- {
- page.Validate();
- return MathF.PI * 0.5f / (page.Tile.AxisTileCount * (page.InteriorSize - 1));
- }
-
- public static PlanetaryPageSample Bilinear(
- ReadOnlySpan samples,
- PlanetaryPageLayout page,
- float2 local)
- {
- page.Validate();
- if (samples.Length != page.StorageSize * page.StorageSize) throw new ArgumentException("Page sample count does not match layout.", nameof(samples));
- var texel = local * (page.InteriorSize - 1) + page.BorderSize;
- var x0 = Math.Clamp((int)MathF.Floor(texel.x), 0, page.StorageSize - 1);
- var y0 = Math.Clamp((int)MathF.Floor(texel.y), 0, page.StorageSize - 1);
- var x1 = Math.Min(x0 + 1, page.StorageSize - 1);
- var y1 = Math.Min(y0 + 1, page.StorageSize - 1);
- var tx = math.frac(texel.x);
- var ty = math.frac(texel.y);
- var a = Lerp(samples[y0 * page.StorageSize + x0], samples[y0 * page.StorageSize + x1], tx);
- var b = Lerp(samples[y1 * page.StorageSize + x0], samples[y1 * page.StorageSize + x1], tx);
- return Lerp(a, b, ty);
- }
-
- public static PlanetaryPageSummary Summarize(ReadOnlySpan samples, PlanetaryPageLayout page, float unresolvedHeightBound)
- {
- page.Validate();
- if (samples.Length != page.StorageSize * page.StorageSize) throw new ArgumentException("Page sample count does not match layout.", nameof(samples));
- var min = float.PositiveInfinity;
- var max = float.NegativeInfinity;
- var maxSlope = 0.0f;
- foreach (var sample in samples)
- {
- min = MathF.Min(min, sample.Height);
- max = MathF.Max(max, sample.Height);
- maxSlope = MathF.Max(maxSlope, math.length(sample.TangentGradient));
- }
- return new(min, max, maxSlope, MathF.Max(unresolvedHeightBound, 0), AngularTexelSize(page));
- }
-
- private static PlanetaryPageSample Lerp(PlanetaryPageSample a, PlanetaryPageSample b, float t) => new(
- math.lerp(a.Height, b.Height, t),
- math.lerp(a.TangentGradient, b.TangentGradient, t),
- math.lerp(a.Ridge, b.Ridge, t),
- math.lerp(a.Gully, b.Gully, t));
-}
-
-public readonly record struct PlanetaryBakedPage(
- PlanetaryPageLayout Layout,
- PlanetaryPageSample[] Samples,
- PlanetaryPageSummary Summary);
-
-public static class PlanetaryPageBaker
-{
- public static PlanetaryBakedPage Bake(
- in PlanetaryFieldDefinition field,
- PlanetaryPageLayout layout,
- TSource source)
- where TSource : IPlanetaryBaseField
- {
- layout.Validate(); field.Validate();
- var spacing = PlanetaryPageSampling.NominalAngularTexelSize(layout) * field.Radius;
- var parentSpacing = layout.Tile.Level == 0
- ? 0
- : PlanetaryPageSampling.NominalAngularTexelSize(new(layout.Tile.Parent(), layout.InteriorSize, layout.BorderSize)) * field.Radius;
- var samples = new PlanetaryPageSample[layout.StorageSize * layout.StorageSize];
- var unresolved = 0.0f;
- for (var y = 0; y < layout.StorageSize; y++)
- for (var x = 0; x < layout.StorageSize; x++)
- {
- var direction = PlanetaryPageSampling.Direction(layout, x, y);
- var baseSample = source.Sample(direction);
- var child = PlanetaryField.Sample(field, direction, baseSample, PlanetaryQueryScale.AtFootprint(spacing));
- var height = child.RadialDisplacement;
- var gradient = child.TangentGradient;
- if (parentSpacing > 0)
- {
- var parent = PlanetaryField.Sample(field, direction, baseSample, PlanetaryQueryScale.AtFootprint(parentSpacing));
- height -= parent.RadialDisplacement;
- gradient -= parent.TangentGradient;
- }
- samples[y * layout.StorageSize + x] = new(height, gradient, child.Ridge, child.Gully);
- unresolved = MathF.Max(unresolved, child.UnresolvedHeightBound);
- }
- return new(layout, samples, PlanetaryPageSampling.Summarize(samples, layout, unresolved));
- }
-}
-
-public readonly record struct PlanetaryResidentPage(PlanetaryBakedPage Page, float Blend)
-{
- public PlanetaryResidentPage Validate()
- {
- if (!float.IsFinite(Blend) || Blend is < 0 or > 1) throw new ArgumentOutOfRangeException(nameof(Blend));
- Page.Layout.Validate();
- if (Page.Samples.Length != Page.Layout.StorageSize * Page.Layout.StorageSize) throw new ArgumentException("Page payload does not match layout.");
- return this;
- }
-}
-
-public static class PlanetaryPageSetSampling
-{
- public static bool TrySample(ReadOnlySpan pages, float3 direction, out PlanetaryPageSample sample)
- {
- PlanetaryTopology.ValidateDirection(direction);
- var height = 0.0f;
- var gradient = float3.zero;
- var ridge = 0.0f;
- var gully = 0.0f;
- var found = false;
- var deepestLevel = -1;
- foreach (var resident in pages)
- {
- resident.Validate();
- if (!PlanetaryTopology.TryLocalCoordinate(direction, resident.Page.Layout.Tile, out var local)) continue;
- var contribution = PlanetaryPageSampling.Bilinear(resident.Page.Samples, resident.Page.Layout, local);
- var blend = math.saturate(resident.Blend);
- height += contribution.Height * blend;
- gradient += contribution.TangentGradient * blend;
- if (resident.Page.Layout.Tile.Level >= deepestLevel)
- {
- ridge = found ? math.lerp(ridge, contribution.Ridge, blend) : contribution.Ridge;
- gully = found ? math.lerp(gully, contribution.Gully, blend) : contribution.Gully;
- deepestLevel = resident.Page.Layout.Tile.Level;
- }
- found = true;
- }
- sample = new(height, gradient, ridge, gully);
- return found;
- }
-}
diff --git a/src/CultMath/PlanetaryPages.cs.meta b/src/CultMath/PlanetaryPages.cs.meta
deleted file mode 100644
index 91fdc27..0000000
--- a/src/CultMath/PlanetaryPages.cs.meta
+++ /dev/null
@@ -1,2 +0,0 @@
-fileFormatVersion: 2
-guid: 7af9a0354faa56348a2e07188739a0ae
\ No newline at end of file
diff --git a/src/CultMath/PlanetaryPatch.cs b/src/CultMath/PlanetaryPatch.cs
deleted file mode 100644
index 30c852f..0000000
--- a/src/CultMath/PlanetaryPatch.cs
+++ /dev/null
@@ -1,45 +0,0 @@
-using System;
-using System.Linq;
-
-namespace CultMath;
-
-public readonly record struct PlanetaryPatchVertex(float2 LocalCoordinate, float3 UnitDirection);
-
-public readonly record struct PlanetaryPatchMesh(
- PlanetaryCubeFace Face,
- int CellsPerAxis,
- PlanetaryPatchVertex[] Vertices,
- int[] Indices);
-
-public static class PlanetaryPatch
-{
- public static PlanetaryPatchMesh CreateFace(PlanetaryCubeFace face, int cellsPerAxis)
- {
- if (!Enum.IsDefined(typeof(PlanetaryCubeFace), face)) throw new ArgumentOutOfRangeException(nameof(face));
- if (cellsPerAxis < 1 || cellsPerAxis > 4096) throw new ArgumentOutOfRangeException(nameof(cellsPerAxis));
- var axisVertices = cellsPerAxis + 1;
- var vertices = new PlanetaryPatchVertex[checked(axisVertices * axisVertices)];
- for (var y = 0; y < axisVertices; y++)
- for (var x = 0; x < axisVertices; x++)
- {
- var local = new float2(x / (float)cellsPerAxis, y / (float)cellsPerAxis);
- vertices[y * axisVertices + x] = new(local, PlanetaryTopology.Direction(new(face, local.x * 2 - 1, local.y * 2 - 1)));
- }
- var indices = new int[checked(cellsPerAxis * cellsPerAxis * 6)];
- var write = 0;
- for (var y = 0; y < cellsPerAxis; y++)
- for (var x = 0; x < cellsPerAxis; x++)
- {
- var lowerLeft = y * axisVertices + x;
- var lowerRight = lowerLeft + 1;
- var upperLeft = lowerLeft + axisVertices;
- var upperRight = upperLeft + 1;
- indices[write++] = lowerLeft; indices[write++] = lowerRight; indices[write++] = upperRight;
- indices[write++] = lowerLeft; indices[write++] = upperRight; indices[write++] = upperLeft;
- }
- return new(face, cellsPerAxis, vertices, indices);
- }
-
- public static PlanetaryPatchMesh[] CreateCubeSphere(int cellsPerAxis)
- => ((PlanetaryCubeFace[])Enum.GetValues(typeof(PlanetaryCubeFace))).Select(face => CreateFace(face, cellsPerAxis)).ToArray();
-}
diff --git a/src/CultMath/PlanetaryPatch.cs.meta b/src/CultMath/PlanetaryPatch.cs.meta
deleted file mode 100644
index efd8156..0000000
--- a/src/CultMath/PlanetaryPatch.cs.meta
+++ /dev/null
@@ -1,2 +0,0 @@
-fileFormatVersion: 2
-guid: 04651571f7d240a418c1f6aafa217fa6
\ No newline at end of file
diff --git a/src/CultMath/PlanetaryProjection.cs b/src/CultMath/PlanetaryProjection.cs
deleted file mode 100644
index a82ebfd..0000000
--- a/src/CultMath/PlanetaryProjection.cs
+++ /dev/null
@@ -1,322 +0,0 @@
-using System;
-
-namespace CultMath;
-
-public enum PlanetaryProjectionKind
-{
- Equirectangular,
- WebMercator,
- EqualEarth,
- Orthographic,
- AzimuthalEquidistant,
- AzimuthalEqualArea,
- CubeAtlas,
- LocalTangent,
-}
-
-public readonly record struct PlanetaryProjectionParameters(
- PlanetaryProjectionKind Kind,
- double CenterLongitude = 0,
- double CenterLatitude = 0,
- double Scale = 1)
-{
- public PlanetaryProjectionParameters Validate()
- {
- if (!double.IsFinite(CenterLongitude) || !double.IsFinite(CenterLatitude) || !double.IsFinite(Scale) || Scale <= 0)
- throw new ArgumentOutOfRangeException(nameof(Scale));
- if (CenterLatitude is < -Math.PI / 2 or > Math.PI / 2) throw new ArgumentOutOfRangeException(nameof(CenterLatitude));
- return this;
- }
-}
-
-public static class PlanetaryProjection
-{
- private const double EqualEarthA1 = 1.340264;
- private const double EqualEarthA2 = -0.081106;
- private const double EqualEarthA3 = 0.000893;
- private const double EqualEarthA4 = 0.003796;
- private static readonly double EqualEarthM = Math.Sqrt(3) / 2;
- private static readonly double EqualEarthXMax = Math.PI / (EqualEarthM * EqualEarthA1);
- private static readonly double EqualEarthYMax = EqualEarthY(Math.Asin(EqualEarthM));
- public static readonly double WebMercatorMaximumLatitude = Math.Atan(Math.Sinh(Math.PI));
-
- public static bool TryForward(float3 direction, PlanetaryProjectionParameters parameters, out double2 coordinate)
- {
- parameters.Validate();
- PlanetaryTopology.ValidateDirection(direction);
- var d = math.normalize(direction);
- if (parameters.Kind == PlanetaryProjectionKind.CubeAtlas)
- {
- var face = PlanetaryTopology.FaceCoordinate(d);
- var index = (int)face.Face;
- var column = index % 3;
- var row = index / 3;
- coordinate = new(
- (-1 + (column + (face.U + 1) * 0.5) * (2.0 / 3.0)) / parameters.Scale,
- (1 - (row + (face.V + 1) * 0.5)) / parameters.Scale);
- return true;
- }
-
- var longitude = Math.Atan2(d.y, d.x);
- var latitude = Math.Asin(Math.Clamp(d.z, -1, 1));
- var deltaLongitude = WrapLongitude(longitude - parameters.CenterLongitude);
- var centered = CenteredForward(deltaLongitude, latitude, parameters, out coordinate);
- coordinate /= parameters.Scale;
- return centered;
- }
-
- public static bool TryInverse(double2 coordinate, PlanetaryProjectionParameters parameters, out float3 direction)
- {
- parameters.Validate();
- if (!double.IsFinite(coordinate.x) || !double.IsFinite(coordinate.y))
- {
- direction = float3.zero;
- return false;
- }
- coordinate *= parameters.Scale;
- if (parameters.Kind == PlanetaryProjectionKind.CubeAtlas)
- return TryInverseCubeAtlas(coordinate, out direction);
-
- if (!CenteredInverse(coordinate, parameters, out var deltaLongitude, out var latitude))
- {
- direction = float3.zero;
- return false;
- }
- var longitude = WrapLongitude(deltaLongitude + parameters.CenterLongitude);
- var cosLatitude = Math.Cos(latitude);
- direction = math.normalize(new float3(
- (float)(cosLatitude * Math.Cos(longitude)),
- (float)(cosLatitude * Math.Sin(longitude)),
- (float)Math.Sin(latitude)));
- return true;
- }
-
- private static bool CenteredForward(double longitude, double latitude, PlanetaryProjectionParameters p, out double2 coordinate)
- {
- switch (p.Kind)
- {
- case PlanetaryProjectionKind.Equirectangular:
- coordinate = new(longitude / Math.PI, latitude / (Math.PI * 0.5));
- return true;
- case PlanetaryProjectionKind.WebMercator:
- if (Math.Abs(latitude) > WebMercatorMaximumLatitude) { coordinate = double2.zero; return false; }
- coordinate = new(longitude / Math.PI, Math.Asinh(Math.Tan(latitude)) / Math.PI);
- return true;
- case PlanetaryProjectionKind.EqualEarth:
- {
- var theta = Math.Asin(EqualEarthM * Math.Sin(latitude));
- var theta2 = theta * theta;
- var theta6 = theta2 * theta2 * theta2;
- var denominator = EqualEarthM * (EqualEarthA1 + 3 * EqualEarthA2 * theta2 + theta6 * (7 * EqualEarthA3 + 9 * EqualEarthA4 * theta2));
- coordinate = new(longitude * Math.Cos(theta) / denominator / EqualEarthXMax, EqualEarthY(theta) / EqualEarthYMax);
- return true;
- }
- case PlanetaryProjectionKind.Orthographic:
- case PlanetaryProjectionKind.AzimuthalEquidistant:
- case PlanetaryProjectionKind.AzimuthalEqualArea:
- case PlanetaryProjectionKind.LocalTangent:
- return ForwardAzimuthal(longitude, latitude, p, out coordinate);
- default:
- throw new ArgumentOutOfRangeException(nameof(p));
- }
- }
-
- private static bool CenteredInverse(double2 coordinate, PlanetaryProjectionParameters p, out double longitude, out double latitude)
- {
- longitude = latitude = 0;
- switch (p.Kind)
- {
- case PlanetaryProjectionKind.Equirectangular:
- if (Math.Abs(coordinate.y) > 1) return false;
- longitude = coordinate.x * Math.PI; latitude = coordinate.y * Math.PI * 0.5; return Math.Abs(coordinate.x) <= 1;
- case PlanetaryProjectionKind.WebMercator:
- if (Math.Abs(coordinate.x) > 1 || Math.Abs(coordinate.y) > 1) return false;
- longitude = coordinate.x * Math.PI; latitude = Math.Atan(Math.Sinh(coordinate.y * Math.PI)); return true;
- case PlanetaryProjectionKind.EqualEarth:
- {
- if (Math.Abs(coordinate.x) > 1 || Math.Abs(coordinate.y) > 1) return false;
- var targetY = coordinate.y * EqualEarthYMax;
- var theta = targetY / EqualEarthA1;
- for (var i = 0; i < 8; i++)
- {
- var theta2 = theta * theta;
- var theta6 = theta2 * theta2 * theta2;
- var f = EqualEarthY(theta) - targetY;
- var derivative = EqualEarthA1 + 3 * EqualEarthA2 * theta2 + theta6 * (7 * EqualEarthA3 + 9 * EqualEarthA4 * theta2);
- theta -= f / derivative;
- }
- var sinLatitude = Math.Sin(theta) / EqualEarthM;
- if (Math.Abs(sinLatitude) > 1.0000000001) return false;
- latitude = Math.Asin(Math.Clamp(sinLatitude, -1, 1));
- var thetaSquared = theta * theta;
- var thetaSixth = thetaSquared * thetaSquared * thetaSquared;
- var denominator = EqualEarthM * (EqualEarthA1 + 3 * EqualEarthA2 * thetaSquared + thetaSixth * (7 * EqualEarthA3 + 9 * EqualEarthA4 * thetaSquared));
- longitude = coordinate.x * EqualEarthXMax * denominator / Math.Cos(theta);
- return Math.Abs(longitude) <= Math.PI + 1.0e-10;
- }
- case PlanetaryProjectionKind.Orthographic:
- case PlanetaryProjectionKind.AzimuthalEquidistant:
- case PlanetaryProjectionKind.AzimuthalEqualArea:
- case PlanetaryProjectionKind.LocalTangent:
- return InverseAzimuthal(coordinate, p, out longitude, out latitude);
- default:
- throw new ArgumentOutOfRangeException(nameof(p));
- }
- }
-
- private static bool ForwardAzimuthal(double longitude, double latitude, PlanetaryProjectionParameters p, out double2 coordinate)
- {
- var sin0 = Math.Sin(p.CenterLatitude); var cos0 = Math.Cos(p.CenterLatitude);
- var sin = Math.Sin(latitude); var cos = Math.Cos(latitude); var cosLon = Math.Cos(longitude);
- var cosC = Math.Clamp(sin0 * sin + cos0 * cos * cosLon, -1, 1);
- var rawX = cos * Math.Sin(longitude);
- var rawY = cos0 * sin - sin0 * cos * cosLon;
- double factor;
- switch (p.Kind)
- {
- case PlanetaryProjectionKind.Orthographic:
- if (cosC < 0) { coordinate = double2.zero; return false; }
- factor = 1; break;
- case PlanetaryProjectionKind.AzimuthalEquidistant:
- {
- var c = Math.Acos(cosC); factor = c < 1.0e-12 ? 1 : c / Math.Sin(c); factor /= Math.PI; break;
- }
- case PlanetaryProjectionKind.AzimuthalEqualArea:
- factor = Math.Sqrt(2 / Math.Max(1 + cosC, 1.0e-20)) * 0.5; break;
- case PlanetaryProjectionKind.LocalTangent:
- if (cosC <= 0) { coordinate = double2.zero; return false; }
- factor = 1 / cosC; break;
- default: throw new ArgumentOutOfRangeException(nameof(p));
- }
- coordinate = new(rawX * factor, rawY * factor);
- return true;
- }
-
- private static bool InverseAzimuthal(double2 coordinate, PlanetaryProjectionParameters p, out double longitude, out double latitude)
- {
- longitude = latitude = 0;
- var rho = Math.Sqrt(coordinate.x * coordinate.x + coordinate.y * coordinate.y);
- double c;
- switch (p.Kind)
- {
- case PlanetaryProjectionKind.Orthographic: if (rho > 1) return false; c = Math.Asin(Math.Clamp(rho, 0, 1)); break;
- case PlanetaryProjectionKind.AzimuthalEquidistant: if (rho > 1) return false; c = rho * Math.PI; break;
- case PlanetaryProjectionKind.AzimuthalEqualArea: if (rho > 1) return false; c = 2 * Math.Asin(Math.Clamp(rho, 0, 1)); break;
- case PlanetaryProjectionKind.LocalTangent: c = Math.Atan(rho); break;
- default: throw new ArgumentOutOfRangeException(nameof(p));
- }
- if (rho < 1.0e-12) { latitude = p.CenterLatitude; return true; }
- var sinC = Math.Sin(c); var cosC = Math.Cos(c);
- var sin0 = Math.Sin(p.CenterLatitude); var cos0 = Math.Cos(p.CenterLatitude);
- latitude = Math.Asin(Math.Clamp(cosC * sin0 + coordinate.y * sinC * cos0 / rho, -1, 1));
- longitude = Math.Atan2(coordinate.x * sinC, rho * cos0 * cosC - coordinate.y * sin0 * sinC);
- return true;
- }
-
- private static bool TryInverseCubeAtlas(double2 coordinate, out float3 direction)
- {
- direction = float3.zero;
- if (coordinate.x is < -1 or > 1 || coordinate.y is < -1 or > 1) return false;
- var column = Math.Min((int)((coordinate.x + 1) * 1.5), 2);
- var row = Math.Min((int)((1 - coordinate.y)), 1);
- var u = ((coordinate.x + 1) * 1.5 - column) * 2 - 1;
- var v = ((1 - coordinate.y) - row) * 2 - 1;
- direction = PlanetaryTopology.Direction(new((PlanetaryCubeFace)(row * 3 + column), u, v));
- return true;
- }
-
- private static double EqualEarthY(double theta)
- {
- var theta2 = theta * theta;
- var theta6 = theta2 * theta2 * theta2;
- return theta * (EqualEarthA1 + EqualEarthA2 * theta2 + theta6 * (EqualEarthA3 + EqualEarthA4 * theta2));
- }
-
- private static double WrapLongitude(double longitude)
- {
- longitude %= Math.PI * 2;
- if (longitude > Math.PI) longitude -= Math.PI * 2;
- if (longitude < -Math.PI) longitude += Math.PI * 2;
- return longitude;
- }
-}
-
-public readonly record struct PlanetaryMapTileLayout(
- PlanetaryProjectionParameters Projection,
- int Level,
- int X,
- int Y,
- int InteriorSize,
- int BorderSize)
-{
- public int StorageSize => checked(InteriorSize + BorderSize * 2);
-
- public PlanetaryMapTileLayout Validate()
- {
- Projection.Validate();
- if (Level is < 0 or > 30) throw new ArgumentOutOfRangeException(nameof(Level));
- var count = 1 << Level;
- if ((uint)X >= (uint)count || (uint)Y >= (uint)count) throw new ArgumentOutOfRangeException(nameof(X));
- if (InteriorSize < 2 || BorderSize < 0 || BorderSize >= InteriorSize) throw new ArgumentOutOfRangeException(nameof(InteriorSize));
- return this;
- }
-}
-
-public static class PlanetaryMapTileSampling
-{
- public static bool TryDirection(PlanetaryMapTileLayout tile, int storageX, int storageY, out float3 direction)
- {
- tile.Validate();
- if ((uint)storageX >= (uint)tile.StorageSize || (uint)storageY >= (uint)tile.StorageSize) throw new ArgumentOutOfRangeException(nameof(storageX));
- var count = 1 << tile.Level;
- var localU = (storageX - tile.BorderSize) / (double)(tile.InteriorSize - 1);
- var localV = (storageY - tile.BorderSize) / (double)(tile.InteriorSize - 1);
- var map = new double2(-1 + 2 * (tile.X + localU) / count, 1 - 2 * (tile.Y + localV) / count);
- return PlanetaryProjection.TryInverse(map, tile.Projection, out direction);
- }
-}
-
-public readonly record struct PlanetaryMapTileKey(
- ulong FieldVersion,
- uint ProjectionVersion,
- uint LayerId,
- PlanetaryMapTileLayout Layout,
- PlanetaryQueryScale QueryScale)
-{
- public PlanetaryMapTileKey Validate()
- {
- if (FieldVersion == 0 || ProjectionVersion == 0) throw new ArgumentOutOfRangeException(nameof(FieldVersion));
- Layout.Validate(); QueryScale.Validate();
- return this;
- }
-}
-
-public readonly record struct PlanetarySurfaceMapTile(
- PlanetaryMapTileKey Key,
- PlanetarySurfaceSample[] Samples,
- bool[] Validity);
-
-public static class PlanetaryMapTileBaker
-{
- public static PlanetarySurfaceMapTile Bake(
- in PlanetaryFieldDefinition field,
- PlanetaryMapTileKey key,
- TSource source)
- where TSource : IPlanetaryBaseField
- {
- field.Validate(); key.Validate();
- if (field.FieldVersion != key.FieldVersion) throw new ArgumentException("Tile field version does not match definition.", nameof(key));
- var count = key.Layout.StorageSize * key.Layout.StorageSize;
- var samples = new PlanetarySurfaceSample[count];
- var validity = new bool[count];
- for (var y = 0; y < key.Layout.StorageSize; y++)
- for (var x = 0; x < key.Layout.StorageSize; x++)
- {
- var index = y * key.Layout.StorageSize + x;
- if (!PlanetaryMapTileSampling.TryDirection(key.Layout, x, y, out var direction)) continue;
- samples[index] = PlanetaryField.Sample(field, direction, source.Sample(direction), key.QueryScale);
- validity[index] = true;
- }
- return new(key, samples, validity);
- }
-}
diff --git a/src/CultMath/PlanetaryProjection.cs.meta b/src/CultMath/PlanetaryProjection.cs.meta
deleted file mode 100644
index 7b1e665..0000000
--- a/src/CultMath/PlanetaryProjection.cs.meta
+++ /dev/null
@@ -1,2 +0,0 @@
-fileFormatVersion: 2
-guid: 5021202cf626f7442a57961255a8a217
\ No newline at end of file
diff --git a/src/CultMath/PlanetaryQueries.cs b/src/CultMath/PlanetaryQueries.cs
deleted file mode 100644
index 54a9a3e..0000000
--- a/src/CultMath/PlanetaryQueries.cs
+++ /dev/null
@@ -1,193 +0,0 @@
-using System;
-
-namespace CultMath;
-
-public readonly record struct PlanetaryRadialBounds(float MinimumDisplacement, float MaximumDisplacement)
-{
- public PlanetaryRadialBounds Validate()
- {
- if (!float.IsFinite(MinimumDisplacement) || !float.IsFinite(MaximumDisplacement) || MinimumDisplacement > MaximumDisplacement)
- throw new ArgumentOutOfRangeException(nameof(MinimumDisplacement));
- return this;
- }
-}
-
-public readonly record struct PlanetaryRayHit(
- double Distance,
- double3 PlanetLocalPosition,
- PlanetarySurfaceSample Surface);
-
-public readonly record struct PlanetaryPathSample(double Distance, PlanetarySurfaceSample Surface);
-
-public readonly record struct PlanetaryRegionSummary(
- float MinimumDisplacement,
- float MaximumDisplacement,
- float MaximumSlope,
- float MaximumUnresolvedHeight);
-
-public static class PlanetaryQueries
-{
- public static bool TryIntersectRay(
- in PlanetaryFieldDefinition field,
- double3 rayOrigin,
- double3 rayDirection,
- double maximumDistance,
- in PlanetaryQueryScale scale,
- in PlanetaryRadialBounds bounds,
- TSource source,
- out PlanetaryRayHit hit)
- where TSource : IPlanetaryBaseField
- {
- field.Validate(); scale.Validate(); bounds.Validate();
- ValidateFinite(rayOrigin, nameof(rayOrigin)); ValidateFinite(rayDirection, nameof(rayDirection));
- if (!double.IsFinite(maximumDistance) || maximumDistance <= 0) throw new ArgumentOutOfRangeException(nameof(maximumDistance));
- var directionLength = Length(rayDirection);
- if (directionLength < 1.0e-15) throw new ArgumentOutOfRangeException(nameof(rayDirection));
- var direction = rayDirection / directionLength;
- var outerRadius = field.Radius + bounds.MaximumDisplacement + scale.MaximumUnresolvedHeight;
- if (!double.IsFinite(outerRadius)) outerRadius = field.Radius + bounds.MaximumDisplacement;
- if (!TrySphereInterval(rayOrigin, direction, outerRadius, out var enter, out var leave)) { hit = default; return false; }
- var start = Math.Max(enter, 0);
- var end = Math.Min(leave, maximumDistance);
- if (start > end) { hit = default; return false; }
-
- var previousDistance = start;
- var previous = SignedSurfaceDistance(field, rayOrigin + direction * previousDistance, scale, source, out var previousSample);
- if (previous <= 0)
- {
- hit = new(previousDistance, rayOrigin + direction * previousDistance, previousSample);
- return true;
- }
- var minimumStep = Math.Max(scale.FootprintMeters * 0.5, field.Radius * 1.0e-7);
- for (var iteration = 0; iteration < 512 && previousDistance < end; iteration++)
- {
- var nextDistance = Math.Min(previousDistance + Math.Max(previous * 0.5, minimumStep), end);
- var next = SignedSurfaceDistance(field, rayOrigin + direction * nextDistance, scale, source, out var nextSample);
- if (next <= 0)
- {
- var low = previousDistance;
- var high = nextDistance;
- var surface = nextSample;
- for (var refine = 0; refine < 32; refine++)
- {
- var middle = (low + high) * 0.5;
- var value = SignedSurfaceDistance(field, rayOrigin + direction * middle, scale, source, out var middleSample);
- if (value > 0) low = middle;
- else { high = middle; surface = middleSample; }
- }
- var position = rayOrigin + direction * high;
- hit = new(high, position, surface);
- return true;
- }
- if (nextDistance >= end) break;
- previousDistance = nextDistance;
- previous = next;
- previousSample = nextSample;
- }
- hit = default;
- return false;
- }
-
- public static double SegmentClearance(
- in PlanetaryFieldDefinition field,
- double3 start,
- double3 end,
- double sampleSpacing,
- in PlanetaryQueryScale scale,
- TSource source)
- where TSource : IPlanetaryBaseField
- {
- ValidateFinite(start, nameof(start)); ValidateFinite(end, nameof(end));
- if (!double.IsFinite(sampleSpacing) || sampleSpacing <= 0) throw new ArgumentOutOfRangeException(nameof(sampleSpacing));
- var delta = end - start;
- var distance = Length(delta);
- var steps = Math.Max((int)Math.Ceiling(distance / sampleSpacing), 1);
- var minimum = double.PositiveInfinity;
- for (var i = 0; i <= steps; i++)
- {
- var position = start + delta * (i / (double)steps);
- minimum = Math.Min(minimum, SignedSurfaceDistance(field, position, scale, source, out _));
- }
- return minimum;
- }
-
- public static PlanetaryPathSample[] SampleGreatCircle(
- in PlanetaryFieldDefinition field,
- float3 startDirection,
- float3 endDirection,
- int segmentCount,
- in PlanetaryQueryScale scale,
- TSource source)
- where TSource : IPlanetaryBaseField
- {
- if (segmentCount < 1) throw new ArgumentOutOfRangeException(nameof(segmentCount));
- PlanetaryTopology.ValidateDirection(startDirection); PlanetaryTopology.ValidateDirection(endDirection);
- var a = math.normalize(startDirection); var b = math.normalize(endDirection);
- var angle = MathF.Acos(math.clamp(math.dot(a, b), -1, 1));
- var sinAngle = MathF.Sin(angle);
- var result = new PlanetaryPathSample[segmentCount + 1];
- for (var i = 0; i <= segmentCount; i++)
- {
- var t = i / (float)segmentCount;
- var direction = sinAngle < 1.0e-6f
- ? math.normalize(math.lerp(a, b, t))
- : math.normalize(a * (MathF.Sin((1 - t) * angle) / sinAngle) + b * (MathF.Sin(t * angle) / sinAngle));
- var sample = PlanetaryField.Sample(field, direction, source.Sample(direction), scale);
- result[i] = new(angle * field.Radius * t, sample);
- }
- return result;
- }
-
- public static PlanetaryRegionSummary Summarize(
- in PlanetaryFieldDefinition field,
- ReadOnlySpan directions,
- in PlanetaryQueryScale scale,
- TSource source)
- where TSource : IPlanetaryBaseField
- {
- if (directions.IsEmpty) throw new ArgumentException("At least one direction is required.", nameof(directions));
- var min = float.PositiveInfinity; var max = float.NegativeInfinity; var slope = 0.0f; var unresolved = 0.0f;
- foreach (var direction in directions)
- {
- var normalized = math.normalize(direction);
- var sample = PlanetaryField.Sample(field, normalized, source.Sample(normalized), scale);
- min = MathF.Min(min, sample.RadialDisplacement);
- max = MathF.Max(max, sample.RadialDisplacement);
- slope = MathF.Max(slope, sample.Slope);
- unresolved = MathF.Max(unresolved, sample.UnresolvedHeightBound);
- }
- return new(min, max, slope, unresolved);
- }
-
- private static double SignedSurfaceDistance(
- in PlanetaryFieldDefinition field,
- double3 position,
- in PlanetaryQueryScale scale,
- TSource source,
- out PlanetarySurfaceSample sample)
- where TSource : IPlanetaryBaseField
- {
- var radius = Length(position);
- if (radius < 1.0e-15) { sample = default; return double.NegativeInfinity; }
- var direction = new float3((float)(position.x / radius), (float)(position.y / radius), (float)(position.z / radius));
- sample = PlanetaryField.Sample(field, direction, source.Sample(direction), scale);
- return radius - sample.SurfaceRadius;
- }
-
- private static bool TrySphereInterval(double3 origin, double3 direction, double radius, out double enter, out double leave)
- {
- var b = Dot(origin, direction);
- var c = Dot(origin, origin) - radius * radius;
- var discriminant = b * b - c;
- if (discriminant < 0) { enter = leave = 0; return false; }
- var root = Math.Sqrt(discriminant);
- enter = -b - root; leave = -b + root; return leave >= 0;
- }
-
- private static double Dot(double3 a, double3 b) => a.x * b.x + a.y * b.y + a.z * b.z;
- private static double Length(double3 value) => Math.Sqrt(Dot(value, value));
- private static void ValidateFinite(double3 value, string name)
- {
- if (!double.IsFinite(value.x) || !double.IsFinite(value.y) || !double.IsFinite(value.z)) throw new ArgumentOutOfRangeException(name);
- }
-}
diff --git a/src/CultMath/PlanetaryQueries.cs.meta b/src/CultMath/PlanetaryQueries.cs.meta
deleted file mode 100644
index 6fd1141..0000000
--- a/src/CultMath/PlanetaryQueries.cs.meta
+++ /dev/null
@@ -1,2 +0,0 @@
-fileFormatVersion: 2
-guid: 2ec43c567214b8247bda19b5c120c200
\ No newline at end of file
diff --git a/src/CultMath/PlanetaryRadialRefinement.cs b/src/CultMath/PlanetaryRadialRefinement.cs
deleted file mode 100644
index 7188a51..0000000
--- a/src/CultMath/PlanetaryRadialRefinement.cs
+++ /dev/null
@@ -1,28 +0,0 @@
-namespace CultMath;
-
-public static class PlanetaryRadialRefinement
-{
- public static float3 Step(
- float3 position,
- float3 rayDirection,
- float3 center,
- float targetRadius,
- float minimumDerivative = 0.2f,
- float maximumCorrection = 0.08f)
- {
- var local = position - center;
- var radius = math.length(local);
- var radial = local / math.max(radius, 0.0001f);
- var error = radius - targetRadius;
- var derivative = math.dot(rayDirection, radial);
- var safeMinimum = math.max(minimumDerivative, 0.0001f);
- var safeDerivative = math.abs(derivative) > safeMinimum
- ? derivative
- : derivative < 0.0f ? -safeMinimum : safeMinimum;
- var correction = math.clamp(
- -error / safeDerivative,
- -math.abs(maximumCorrection),
- math.abs(maximumCorrection));
- return position + rayDirection * correction;
- }
-}
diff --git a/src/CultMath/PlanetaryRadialRefinement.cs.meta b/src/CultMath/PlanetaryRadialRefinement.cs.meta
deleted file mode 100644
index 32af265..0000000
--- a/src/CultMath/PlanetaryRadialRefinement.cs.meta
+++ /dev/null
@@ -1,2 +0,0 @@
-fileFormatVersion: 2
-guid: 82e4db521edc253499220458b417fd1e
\ No newline at end of file
diff --git a/src/CultMath/PlanetaryResidency.cs b/src/CultMath/PlanetaryResidency.cs
deleted file mode 100644
index 6918ac5..0000000
--- a/src/CultMath/PlanetaryResidency.cs
+++ /dev/null
@@ -1,148 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-
-namespace CultMath;
-
-public readonly record struct PlanetaryLodParameters(
- int MaximumLevel,
- int PageInteriorSize,
- int PageBorderSize,
- float ViewportHeightPixels,
- float MinimumFootprintMeters)
-{
- public PlanetaryLodParameters Validate()
- {
- if (MaximumLevel is < 0 or > PlanetaryTileAddress.MaxLevel) throw new ArgumentOutOfRangeException(nameof(MaximumLevel));
- _ = new PlanetaryPageLayout(new(PlanetaryCubeFace.PositiveX, 0, 0, 0), PageInteriorSize, PageBorderSize).Validate();
- if (!float.IsFinite(ViewportHeightPixels) || ViewportHeightPixels <= 0) throw new ArgumentOutOfRangeException(nameof(ViewportHeightPixels));
- if (!float.IsFinite(MinimumFootprintMeters) || MinimumFootprintMeters < 0) throw new ArgumentOutOfRangeException(nameof(MinimumFootprintMeters));
- return this;
- }
-}
-
-public static class PlanetaryLodSelector
-{
- public static int SelectLevel(
- in PlanetaryFieldDefinition field,
- float cameraDistanceFromCenter,
- in PlanetaryLodParameters parameters)
- {
- field.Validate(); parameters.Validate();
- if (!float.IsFinite(cameraDistanceFromCenter) || cameraDistanceFromCenter < 0) throw new ArgumentOutOfRangeException(nameof(cameraDistanceFromCenter));
- var altitude = MathF.Max(cameraDistanceFromCenter - field.Radius, 0.001f);
- var footprint = MathF.Max(altitude / parameters.ViewportHeightPixels * 2, parameters.MinimumFootprintMeters);
- var erosion = field.Erosion;
- for (var level = 0; level <= parameters.MaximumLevel; level++)
- {
- var page = new PlanetaryPageLayout(new(PlanetaryCubeFace.PositiveX, level, 0, 0), parameters.PageInteriorSize, parameters.PageBorderSize);
- var spacing = PlanetaryPageSampling.NominalAngularTexelSize(page) * field.Radius;
- var unresolved = ErosionFrequencyBands.Select(
- erosion.Scale * erosion.CellScale, spacing, erosion.Octaves,
- erosion.Lacunarity, erosion.Strength * erosion.Scale, erosion.Gain).UnresolvedHeightBound;
- if (unresolved <= footprint) return level;
- }
- return parameters.MaximumLevel;
- }
-
- public static PlanetaryTileAddress[] SelectAncestorChain(
- in PlanetaryFieldDefinition field,
- float3 cameraDirection,
- float cameraDistanceFromCenter,
- in PlanetaryLodParameters parameters)
- {
- PlanetaryTopology.ValidateDirection(cameraDirection);
- var level = SelectLevel(field, cameraDistanceFromCenter, parameters);
- var result = new PlanetaryTileAddress[6 + level];
- for (var face = 0; face < 6; face++) result[face] = new((PlanetaryCubeFace)face, 0, 0, 0);
- var leaf = PlanetaryTopology.TileAt(cameraDirection, level);
- for (var currentLevel = level; currentLevel >= 1; currentLevel--)
- {
- result[6 + currentLevel - 1] = leaf;
- leaf = leaf.Parent();
- }
- return result;
- }
-}
-
-public readonly record struct PlanetaryResidentTile(PlanetaryTileAddress Tile, float Blend, bool Departing);
-
-public readonly record struct PlanetaryResidencySnapshot(
- ulong ContentVersion,
- ulong PresentationVersion,
- PlanetaryResidentTile[] Tiles);
-
-/// Owns presentation residency only. It never owns field content.
-public sealed class PlanetaryResidualResidency
-{
- private readonly Dictionary residents = new();
- private readonly Dictionary arrivals = new();
- private readonly Dictionary departures = new();
- private float lastTime = float.NegativeInfinity;
-
- public void Reset()
- {
- residents.Clear(); arrivals.Clear(); departures.Clear(); lastTime = float.NegativeInfinity;
- }
-
- public PlanetaryResidencySnapshot Update(ReadOnlySpan desired, float timeSeconds, float transitionSeconds)
- {
- if (!float.IsFinite(timeSeconds)) throw new ArgumentOutOfRangeException(nameof(timeSeconds));
- if (!float.IsFinite(transitionSeconds) || transitionSeconds < 0) throw new ArgumentOutOfRangeException(nameof(transitionSeconds));
- if (timeSeconds < lastTime) Reset();
- lastTime = timeSeconds;
-
- var desiredKeys = new HashSet();
- foreach (var tile in desired)
- {
- var key = tile.StableKey;
- desiredKeys.Add(key);
- residents[key] = tile;
- if (!arrivals.ContainsKey(key)) arrivals[key] = timeSeconds;
- departures.Remove(key);
- }
-
- foreach (var pair in residents.ToArray())
- {
- if (pair.Value.Level == 0 || desiredKeys.Contains(pair.Key)) continue;
- if (!departures.ContainsKey(pair.Key))
- {
- var startingBlend = transitionSeconds == 0
- ? 1
- : Math.Clamp((timeSeconds - arrivals[pair.Key]) / transitionSeconds, 0, 1);
- departures[pair.Key] = (timeSeconds, startingBlend);
- }
- if (transitionSeconds > 0 && timeSeconds - departures[pair.Key].Time < transitionSeconds) continue;
- residents.Remove(pair.Key); arrivals.Remove(pair.Key); departures.Remove(pair.Key);
- }
-
- var ordered = residents
- .OrderBy(pair => pair.Value.Level)
- .ThenBy(pair => pair.Value.Face)
- .ThenBy(pair => pair.Value.X)
- .ThenBy(pair => pair.Value.Y)
- .ToArray();
- var tiles = new PlanetaryResidentTile[ordered.Length];
- for (var i = 0; i < ordered.Length; i++)
- {
- var pair = ordered[i];
- var departing = departures.TryGetValue(pair.Key, out var departure);
- var blend = pair.Value.Level == 0 || transitionSeconds == 0
- ? 1
- : departing
- ? departure.StartingBlend * Math.Clamp(1 - (timeSeconds - departure.Time) / transitionSeconds, 0, 1)
- : Math.Clamp((timeSeconds - arrivals[pair.Key]) / transitionSeconds, 0, 1);
- tiles[i] = new(pair.Value, blend, departing);
- }
- return new(Hash(ordered.Select(pair => pair.Key)), Hash(tiles.Select(tile => tile.Tile.StableKey ^ (uint)BitConverter.SingleToInt32Bits(tile.Blend))), tiles);
- }
-
- private static ulong Hash(IEnumerable values)
- {
- const ulong offset = 14695981039346656037UL;
- const ulong prime = 1099511628211UL;
- var hash = offset;
- foreach (var value in values) hash = unchecked((hash ^ value) * prime);
- return hash == 0 ? 1 : hash;
- }
-}
diff --git a/src/CultMath/PlanetaryResidency.cs.meta b/src/CultMath/PlanetaryResidency.cs.meta
deleted file mode 100644
index 2434762..0000000
--- a/src/CultMath/PlanetaryResidency.cs.meta
+++ /dev/null
@@ -1,2 +0,0 @@
-fileFormatVersion: 2
-guid: 0585ea15edd7f1c4fa272df4911a8d6f
\ No newline at end of file
diff --git a/src/CultMath/PlanetaryTopology.cs b/src/CultMath/PlanetaryTopology.cs
deleted file mode 100644
index 57d562c..0000000
--- a/src/CultMath/PlanetaryTopology.cs
+++ /dev/null
@@ -1,191 +0,0 @@
-using System;
-
-namespace CultMath;
-
-public enum PlanetaryCubeFace
-{
- PositiveX,
- NegativeX,
- PositiveY,
- NegativeY,
- PositiveZ,
- NegativeZ,
-}
-
-public readonly record struct PlanetaryFaceCoordinate(PlanetaryCubeFace Face, double U, double V)
-{
- public PlanetaryFaceCoordinate Validate(bool allowOutsideFace = false)
- {
- if (!double.IsFinite(U) || !double.IsFinite(V)) throw new ArgumentOutOfRangeException(nameof(U));
- if (!allowOutsideFace && (U is < -1.0 or > 1.0 || V is < -1.0 or > 1.0))
- throw new ArgumentOutOfRangeException(nameof(U), "Face coordinates must be in [-1, 1].");
- if (!Enum.IsDefined(typeof(PlanetaryCubeFace), Face)) throw new ArgumentOutOfRangeException(nameof(Face));
- return this;
- }
-}
-
-public readonly record struct PlanetaryTileAddress
-{
- public const int MaxLevel = 30;
-
- public PlanetaryTileAddress(PlanetaryCubeFace face, int level, int x, int y)
- {
- if (!Enum.IsDefined(typeof(PlanetaryCubeFace), face)) throw new ArgumentOutOfRangeException(nameof(face));
- if (level is < 0 or > MaxLevel) throw new ArgumentOutOfRangeException(nameof(level));
- var count = 1 << level;
- if ((uint)x >= (uint)count) throw new ArgumentOutOfRangeException(nameof(x));
- if ((uint)y >= (uint)count) throw new ArgumentOutOfRangeException(nameof(y));
- Face = face;
- Level = level;
- X = x;
- Y = y;
- }
-
- public PlanetaryCubeFace Face { get; }
- public int Level { get; }
- public int X { get; }
- public int Y { get; }
- public int AxisTileCount => 1 << Level;
-
- public PlanetaryTileAddress Parent() => Level == 0
- ? throw new InvalidOperationException("Root cube tile has no parent.")
- : new(Face, Level - 1, X >> 1, Y >> 1);
-
- public PlanetaryTileAddress Child(int childX, int childY)
- {
- if ((uint)childX > 1) throw new ArgumentOutOfRangeException(nameof(childX));
- if ((uint)childY > 1) throw new ArgumentOutOfRangeException(nameof(childY));
- if (Level == MaxLevel) throw new InvalidOperationException($"Level {MaxLevel} cannot be subdivided.");
- return new(Face, Level + 1, (X << 1) | childX, (Y << 1) | childY);
- }
-
- public PlanetaryFaceCoordinate PositionAt(double localU, double localV)
- {
- if (!double.IsFinite(localU) || localU is < 0.0 or > 1.0) throw new ArgumentOutOfRangeException(nameof(localU));
- if (!double.IsFinite(localV) || localV is < 0.0 or > 1.0) throw new ArgumentOutOfRangeException(nameof(localV));
- var count = AxisTileCount;
- return new(Face, -1.0 + 2.0 * (X + localU) / count, -1.0 + 2.0 * (Y + localV) / count);
- }
-
- public ulong StableKey
- {
- get
- {
- const ulong offset = 14695981039346656037UL;
- const ulong prime = 1099511628211UL;
- var hash = offset;
- hash = unchecked((hash ^ (uint)Face) * prime);
- hash = unchecked((hash ^ (uint)Level) * prime);
- hash = unchecked((hash ^ (uint)X) * prime);
- hash = unchecked((hash ^ (uint)Y) * prime);
- return hash == 0 ? 1 : hash;
- }
- }
- public override string ToString() => $"{Face}/L{Level:D2}/{X}/{Y}";
-}
-
-public static class PlanetaryTopology
-{
- private const float QuarterPi = MathF.PI * 0.25f;
-
- public static float3 Direction(PlanetaryFaceCoordinate coordinate)
- {
- coordinate.Validate(allowOutsideFace: true);
- var u = MathF.Tan((float)coordinate.U * QuarterPi);
- var v = MathF.Tan((float)coordinate.V * QuarterPi);
- var cube = coordinate.Face switch
- {
- PlanetaryCubeFace.PositiveX => new float3(1, v, -u),
- PlanetaryCubeFace.NegativeX => new float3(-1, v, u),
- PlanetaryCubeFace.PositiveY => new float3(u, 1, -v),
- PlanetaryCubeFace.NegativeY => new float3(u, -1, v),
- PlanetaryCubeFace.PositiveZ => new float3(u, v, 1),
- PlanetaryCubeFace.NegativeZ => new float3(-u, v, -1),
- _ => throw new ArgumentOutOfRangeException(nameof(coordinate)),
- };
- return math.normalize(cube);
- }
-
- public static float3 NormalizedCubeDirection(PlanetaryFaceCoordinate coordinate)
- {
- coordinate.Validate(allowOutsideFace: true);
- var u = (float)coordinate.U;
- var v = (float)coordinate.V;
- return math.normalize(CubeVector(coordinate.Face, u, v));
- }
-
- public static PlanetaryFaceCoordinate FaceCoordinate(float3 direction)
- {
- ValidateDirection(direction);
- var d = math.normalize(direction);
- var a = math.abs(d);
- if (a.x >= a.y && a.x >= a.z)
- return d.x >= 0
- ? FromCubeUv(PlanetaryCubeFace.PositiveX, -d.z / a.x, d.y / a.x)
- : FromCubeUv(PlanetaryCubeFace.NegativeX, d.z / a.x, d.y / a.x);
- if (a.y >= a.z)
- return d.y >= 0
- ? FromCubeUv(PlanetaryCubeFace.PositiveY, d.x / a.y, -d.z / a.y)
- : FromCubeUv(PlanetaryCubeFace.NegativeY, d.x / a.y, d.z / a.y);
- return d.z >= 0
- ? FromCubeUv(PlanetaryCubeFace.PositiveZ, d.x / a.z, d.y / a.z)
- : FromCubeUv(PlanetaryCubeFace.NegativeZ, -d.x / a.z, d.y / a.z);
- }
-
- public static PlanetaryTileAddress TileAt(float3 direction, int level)
- {
- if (level is < 0 or > PlanetaryTileAddress.MaxLevel) throw new ArgumentOutOfRangeException(nameof(level));
- var face = FaceCoordinate(direction);
- var count = 1 << level;
- var x = Math.Clamp((int)((face.U * 0.5 + 0.5) * count), 0, count - 1);
- var y = Math.Clamp((int)((face.V * 0.5 + 0.5) * count), 0, count - 1);
- return new(face.Face, level, x, y);
- }
-
- public static bool TryLocalCoordinate(float3 direction, PlanetaryTileAddress tile, out float2 local)
- {
- var face = FaceCoordinate(direction);
- if (face.Face != tile.Face) { local = float2.zero; return false; }
- var scaledU = (face.U * 0.5 + 0.5) * tile.AxisTileCount;
- var scaledV = (face.V * 0.5 + 0.5) * tile.AxisTileCount;
- local = new((float)(scaledU - tile.X), (float)(scaledV - tile.Y));
- return local.x is >= 0 and <= 1 && local.y is >= 0 and <= 1;
- }
-
- public static float3 SurfaceNormal(float3 unitDirection, float3 worldDistanceTangentGradient)
- {
- ValidateDirection(unitDirection);
- ValidateFinite(worldDistanceTangentGradient, nameof(worldDistanceTangentGradient));
- var direction = math.normalize(unitDirection);
- var tangent = worldDistanceTangentGradient - direction * math.dot(worldDistanceTangentGradient, direction);
- return math.normalize(direction - tangent);
- }
-
- private static PlanetaryFaceCoordinate FromCubeUv(PlanetaryCubeFace face, float cubeU, float cubeV) => new(
- face,
- MathF.Atan(cubeU) / QuarterPi,
- MathF.Atan(cubeV) / QuarterPi);
-
- private static float3 CubeVector(PlanetaryCubeFace face, float u, float v) => face switch
- {
- PlanetaryCubeFace.PositiveX => new float3(1, v, -u),
- PlanetaryCubeFace.NegativeX => new float3(-1, v, u),
- PlanetaryCubeFace.PositiveY => new float3(u, 1, -v),
- PlanetaryCubeFace.NegativeY => new float3(u, -1, v),
- PlanetaryCubeFace.PositiveZ => new float3(u, v, 1),
- PlanetaryCubeFace.NegativeZ => new float3(-u, v, -1),
- _ => throw new ArgumentOutOfRangeException(nameof(face)),
- };
-
- internal static void ValidateDirection(float3 direction)
- {
- ValidateFinite(direction, nameof(direction));
- if (math.length(direction) < 1.0e-12f) throw new ArgumentOutOfRangeException(nameof(direction));
- }
-
- internal static void ValidateFinite(float3 value, string name)
- {
- if (!float.IsFinite(value.x) || !float.IsFinite(value.y) || !float.IsFinite(value.z))
- throw new ArgumentOutOfRangeException(name);
- }
-}
diff --git a/src/CultMath/PlanetaryTopology.cs.meta b/src/CultMath/PlanetaryTopology.cs.meta
deleted file mode 100644
index 411a8bd..0000000
--- a/src/CultMath/PlanetaryTopology.cs.meta
+++ /dev/null
@@ -1,2 +0,0 @@
-fileFormatVersion: 2
-guid: 88f6789c4384f7846ac4be37613fe5ed
\ No newline at end of file
diff --git a/src/CultMath/SphericalErosion.cs b/src/CultMath/SphericalErosion.cs
deleted file mode 100644
index 93ddb0e..0000000
--- a/src/CultMath/SphericalErosion.cs
+++ /dev/null
@@ -1,108 +0,0 @@
-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);
-
-///
-/// 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.
-///
-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));
-}
diff --git a/src/CultMath/SphericalErosion.cs.meta b/src/CultMath/SphericalErosion.cs.meta
deleted file mode 100644
index 82db773..0000000
--- a/src/CultMath/SphericalErosion.cs.meta
+++ /dev/null
@@ -1,2 +0,0 @@
-fileFormatVersion: 2
-guid: 59ec2db73065f6c4483ad90f5e17850e
\ No newline at end of file
diff --git a/src/CultMath/math.cs b/src/CultMath/math.cs
index e15fbf0..d0a56e7 100644
--- a/src/CultMath/math.cs
+++ b/src/CultMath/math.cs
@@ -22,9 +22,6 @@ public static class math
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);
public static float sin(float value) => MathF.Sin(value);
diff --git a/src/CultMath/rect.cs b/src/CultMath/rect.cs
deleted file mode 100644
index 67b210b..0000000
--- a/src/CultMath/rect.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-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;
- }
-}
diff --git a/src/CultMath/rect.cs.meta b/src/CultMath/rect.cs.meta
deleted file mode 100644
index 9f7598e..0000000
--- a/src/CultMath/rect.cs.meta
+++ /dev/null
@@ -1,2 +0,0 @@
-fileFormatVersion: 2
-guid: b00dc5f108967db47ac2f54a47033e33
\ No newline at end of file
diff --git a/tests/CultMath.Tests/AdvancedErosionFilterTests.cs b/tests/CultMath.Tests/AdvancedErosionFilterTests.cs
deleted file mode 100644
index cccebd6..0000000
--- a/tests/CultMath.Tests/AdvancedErosionFilterTests.cs
+++ /dev/null
@@ -1,44 +0,0 @@
-using Xunit;
-
-namespace CultMath.Tests;
-
-public sealed class AdvancedErosionFilterTests
-{
- [Fact]
- public void DefaultReferenceFixtureIsStable()
- {
- var result = AdvancedErosionFilter.Sample(
- new float2(0.42f, 0.31f),
- new float3(0.5f, 0.1f, -0.2f),
- 0.0f,
- AdvancedErosionParameters.Default);
-
- Assert.Equal(0.0639375f, result.Magnitude, precision: 7);
- Assert.True(float.IsFinite(result.Delta.x));
- Assert.True(float.IsFinite(result.Delta.y));
- Assert.True(float.IsFinite(result.Delta.z));
- Assert.InRange(result.RidgeMap, -1.0f, 1.0f);
- }
-
- [Fact]
- public void FlatInputAndZeroRoundingRemainFinite()
- {
- var parameters = AdvancedErosionParameters.Default with { Rounding = float4.zero };
- var result = AdvancedErosionFilter.Sample(new float2(0.25f, 0.75f), float3.zero, 0.0f, parameters);
-
- Assert.True(float.IsFinite(result.Delta.x));
- Assert.True(float.IsFinite(result.RidgeMap));
- Assert.True(float.IsFinite(result.FadeTarget));
- }
-
- [Fact]
- public void PhacellePartialNormalizationIsFiniteAtEndpoints()
- {
- foreach (var normalization in new[] { 0.0f, 0.5f, 1.0f })
- {
- var sample = AdvancedErosionFilter.Phacelle(new float2(1.7f, -3.2f), new float2(0.6f, -0.8f), 0.7f, 0.25f, normalization);
- Assert.True(float.IsFinite(sample.x));
- Assert.True(float.IsFinite(sample.y));
- }
- }
-}
diff --git a/tests/CultMath.Tests/AdvancedErosionFilterTests.cs.meta b/tests/CultMath.Tests/AdvancedErosionFilterTests.cs.meta
deleted file mode 100644
index 253ee42..0000000
--- a/tests/CultMath.Tests/AdvancedErosionFilterTests.cs.meta
+++ /dev/null
@@ -1,2 +0,0 @@
-fileFormatVersion: 2
-guid: a7c85309318ae3f4ba75ac28a4f661cf
\ No newline at end of file
diff --git a/tests/CultMath.Tests/ErosionFrequencyBandsTests.cs b/tests/CultMath.Tests/ErosionFrequencyBandsTests.cs
deleted file mode 100644
index f57213a..0000000
--- a/tests/CultMath.Tests/ErosionFrequencyBandsTests.cs
+++ /dev/null
@@ -1,34 +0,0 @@
-using Xunit;
-
-namespace CultMath.Tests;
-
-public sealed class ErosionFrequencyBandsTests
-{
- [Fact]
- public void RadiusIndependentWorldWavelengthSelectsSameBands()
- {
- var small = ErosionFrequencyBands.Select(1_000.0f, 100.0f, 8, 2.0f, 20.0f, 0.5f);
- var large = ErosionFrequencyBands.Select(1_000.0f, 100.0f, 8, 2.0f, 20.0f, 0.5f);
- Assert.Equal(small, large);
- Assert.Equal(3, small.ActiveOctaves);
- }
-
- [Fact]
- public void TransitionPartiallyWeightsLastResolvableOctave()
- {
- var selection = ErosionFrequencyBands.Select(1_000.0f, 50.0f, 8, 2.0f, 1.0f, 0.5f);
- Assert.Equal(4, selection.ActiveOctaves);
- Assert.InRange(selection.FinalOctaveWeight, 0.0f, 1.0f);
- Assert.True(selection.UnresolvedHeightBound > 0.0f);
- }
-
- [Fact]
- public void BandedFilterPreservesLowOctaveResultExactly()
- {
- var p = AdvancedErosionParameters.Default;
- var band = new ErosionBandSelection(2, 1.0f, 0.0f, 0.0f);
- var banded = AdvancedErosionFilter.Sample(new float2(0.42f, 0.31f), new float3(0.5f, 0.1f, -0.2f), 0.0f, p, band);
- var truncated = AdvancedErosionFilter.Sample(new float2(0.42f, 0.31f), new float3(0.5f, 0.1f, -0.2f), 0.0f, p with { Octaves = 2 });
- Assert.Equal(truncated, banded);
- }
-}
diff --git a/tests/CultMath.Tests/ErosionFrequencyBandsTests.cs.meta b/tests/CultMath.Tests/ErosionFrequencyBandsTests.cs.meta
deleted file mode 100644
index ef15b7e..0000000
--- a/tests/CultMath.Tests/ErosionFrequencyBandsTests.cs.meta
+++ /dev/null
@@ -1,2 +0,0 @@
-fileFormatVersion: 2
-guid: 095f8ed13d2f2f74baefd23310395256
\ No newline at end of file
diff --git a/tests/CultMath.Tests/HlslMirrorTests.cs b/tests/CultMath.Tests/HlslMirrorTests.cs
index 47f5897..b9396bd 100644
--- a/tests/CultMath.Tests/HlslMirrorTests.cs
+++ b/tests/CultMath.Tests/HlslMirrorTests.cs
@@ -5,22 +5,13 @@ namespace CultMath.Tests;
public sealed class HlslMirrorTests
{
[Fact]
- public void HlslMirrorPublishesSphericalErosionKernel()
+ public void HlslMirrorDoesNotPublishGeometryKernels()
{
var source = File.ReadAllText(Path.Combine(GetRepositoryRoot(), "shaders", "CultMath.hlsl"));
- Assert.Contains("cultmath_spherical_erosion", source);
- Assert.Contains("CultMathSphericalErosionParameters", source);
- }
-
- [Fact]
- public void HlslMirrorPublishesFaithfulAdvancedErosionKernel()
- {
- var mirror = Path.Combine(GetRepositoryRoot(), "shaders", "CultMath.hlsl");
- var source = File.ReadAllText(mirror);
- Assert.Contains("AdvancedErosionFilter.hlsl", source);
- var advanced = File.ReadAllText(Path.Combine(Path.GetDirectoryName(mirror)!, "AdvancedErosionFilter.hlsl"));
- Assert.Contains("cultmath_phacelle_noise", advanced);
- Assert.Contains("cultmath_advanced_erosion_filter", advanced);
+ Assert.DoesNotContain("AdvancedErosionFilter.hlsl", source);
+ Assert.DoesNotContain("Planetary.hlsl", source);
+ Assert.DoesNotContain("spherical_erosion", source);
+ Assert.DoesNotContain("planetary_radial_refinement", source);
}
[Fact]
@@ -66,24 +57,6 @@ public sealed class HlslMirrorTests
Assert.Contains("max(length(value), CULTMATH_NORMALIZE_EPSILON)", include);
}
- [Fact]
- public void HlslMirrorPublishesPlanetaryTopologyAndField()
- {
- var root = GetRepositoryRoot();
- var include = File.ReadAllText(Path.Combine(root, "shaders", "CultMath.hlsl"));
- var planetary = File.ReadAllText(Path.Combine(root, "shaders", "Planetary.hlsl"));
- Assert.Contains("Planetary.hlsl", include);
- Assert.Contains("cultmath_planetary_face_direction", planetary);
- Assert.Contains("cultmath_planetary_face_coordinate", planetary);
- Assert.Contains("cultmath_planetary_page_local", planetary);
- Assert.Contains("cultmath_planetary_field_sample", planetary);
- Assert.Contains("cultmath_planetary_equirectangular_forward", planetary);
- Assert.Contains("cultmath_planetary_equal_earth_forward", planetary);
- Assert.Contains("cultmath_planetary_projection_forward", planetary);
- Assert.Contains("cultmath_planetary_projection_inverse", planetary);
- Assert.Contains("CultMathPlanetarySurfaceSample", planetary);
- }
-
private static string GetRepositoryRoot()
{
var directory = new DirectoryInfo(AppContext.BaseDirectory);
diff --git a/tests/CultMath.Tests/MathTests.cs b/tests/CultMath.Tests/MathTests.cs
index 6979bdd..ad26821 100644
--- a/tests/CultMath.Tests/MathTests.cs
+++ b/tests/CultMath.Tests/MathTests.cs
@@ -32,19 +32,6 @@ 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()
{
diff --git a/tests/CultMath.Tests/PlanetaryFieldTests.cs b/tests/CultMath.Tests/PlanetaryFieldTests.cs
deleted file mode 100644
index 4dc7532..0000000
--- a/tests/CultMath.Tests/PlanetaryFieldTests.cs
+++ /dev/null
@@ -1,80 +0,0 @@
-using Xunit;
-
-namespace CultMath.Tests;
-
-public sealed class PlanetaryFieldTests
-{
- private static readonly PlanetaryFieldDefinition Definition = new(
- 0x7f59_6d11_02ab_9c31UL,
- 8.4f,
- 42,
- AdvancedErosionParameters.Default with { Scale = 0.63f, Strength = 0.12f, Octaves = 7 });
-
- [Fact]
- public void SampleIsDeterministicScaleAwareAndTangent()
- {
- var direction = math.normalize(new float3(0.31f, -0.72f, 0.61f));
- var source = new FixtureField();
- var coarse = PlanetaryField.Sample(Definition, direction, source.Sample(direction), PlanetaryQueryScale.AtFootprint(0.1f));
- var fine = PlanetaryField.Sample(Definition, direction, source.Sample(direction), PlanetaryQueryScale.AtFootprint(0.01f));
- var repeated = PlanetaryField.Sample(Definition, direction, source.Sample(direction), PlanetaryQueryScale.AtFootprint(0.01f));
-
- Assert.Equal(fine, repeated);
- Assert.Equal(Definition.FieldVersion, fine.FieldVersion);
- Assert.True(fine.UnresolvedHeightBound <= coarse.UnresolvedHeightBound);
- Assert.True(fine.FinestResolvedWavelength <= coarse.FinestResolvedWavelength);
- Assert.InRange(MathF.Abs(math.dot(fine.UnitDirection, fine.TangentGradient)), 0, 2.0e-5f);
- Assert.Equal(1, math.length(fine.SurfaceNormal), 5);
- }
-
- [Fact]
- public void BatchAndDoublePositionUseThePointContract()
- {
- var directions = new[]
- {
- math.normalize(new float3(1, 2, 3)),
- math.normalize(new float3(-2, 0.5f, 1)),
- math.normalize(new float3(0.1f, -0.2f, -1)),
- };
- var output = new PlanetarySurfaceSample[directions.Length];
- var source = new FixtureField();
- var scale = PlanetaryQueryScale.AtFootprint(0.05f);
- PlanetaryField.SampleBatch(Definition, directions, scale, source, output);
-
- for (var i = 0; i < directions.Length; i++)
- {
- var normalized = math.normalize(directions[i]);
- var expected = PlanetaryField.Sample(Definition, normalized, source.Sample(normalized), scale);
- Assert.InRange(MathF.Abs(expected.RadialDisplacement - output[i].RadialDisplacement), 0, 1.0e-6f);
- Assert.InRange(math.distance(expected.TangentGradient, output[i].TangentGradient), 0, 1.0e-6f);
- }
-
- var d = directions[0];
- var position = new double3(d.x * 8.0e11, d.y * 8.0e11, d.z * 8.0e11);
- var positioned = PlanetaryField.SamplePosition(Definition, position, scale, source);
- Assert.InRange(math.distance(positioned.UnitDirection, d), 0, 2.0e-6f);
- }
-
- [Fact]
- public void FieldIdentityChangesWithEveryLoadBearingInput()
- {
- var erosion = AdvancedErosionParameters.Default;
- var baseline = PlanetaryFieldDefinition.Create(3, 8.4f, 7, erosion);
- Assert.Equal(baseline.FieldVersion, PlanetaryFieldDefinition.Create(3, 8.4f, 7, erosion).FieldVersion);
- Assert.NotEqual(baseline.FieldVersion, PlanetaryFieldDefinition.Create(4, 8.4f, 7, erosion).FieldVersion);
- Assert.NotEqual(baseline.FieldVersion, PlanetaryFieldDefinition.Create(3, 8.5f, 7, erosion).FieldVersion);
- Assert.NotEqual(baseline.FieldVersion, PlanetaryFieldDefinition.Create(3, 8.4f, 8, erosion).FieldVersion);
- Assert.NotEqual(baseline.FieldVersion, PlanetaryFieldDefinition.Create(3, 8.4f, 7, erosion with { Gain = 0.49f }).FieldVersion);
- }
-
- private readonly struct FixtureField : IPlanetaryBaseField
- {
- public PlanetaryBaseFieldSample Sample(float3 direction)
- {
- var value = direction.x * 0.6f + direction.y * direction.z * 0.3f;
- var fieldGradient = new float3(0.6f, direction.z * 0.3f, direction.y * 0.3f);
- fieldGradient -= direction * math.dot(fieldGradient, direction);
- return new(value * 0.1f, fieldGradient * 0.1f, value, fieldGradient, math.clamp(value, -1, 1));
- }
- }
-}
diff --git a/tests/CultMath.Tests/PlanetaryFieldTests.cs.meta b/tests/CultMath.Tests/PlanetaryFieldTests.cs.meta
deleted file mode 100644
index 1710b5f..0000000
--- a/tests/CultMath.Tests/PlanetaryFieldTests.cs.meta
+++ /dev/null
@@ -1,2 +0,0 @@
-fileFormatVersion: 2
-guid: c747cfda80df21d41a57412d8d07c08a
\ No newline at end of file
diff --git a/tests/CultMath.Tests/PlanetaryGpuPageTests.cs b/tests/CultMath.Tests/PlanetaryGpuPageTests.cs
deleted file mode 100644
index ea17b4b..0000000
--- a/tests/CultMath.Tests/PlanetaryGpuPageTests.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-using Xunit;
-
-namespace CultMath.Tests;
-
-public sealed class PlanetaryGpuPageTests
-{
- [Fact]
- public void UploadContentUsesCanonicalDirectionsSpacingAndMetadata()
- {
- var layout = new PlanetaryPageLayout(new(PlanetaryCubeFace.NegativeY, 3, 4, 2), 9, 2);
- var content = PlanetaryGpuPageBuilder.BuildContent(layout, 8.4f);
- Assert.Equal(layout.StorageSize * layout.StorageSize, content.Inputs.Length);
- Assert.True(content.ParentSampleSpacing > content.SampleSpacing);
- var first = content.Inputs[0];
- Assert.Equal(PlanetaryPageSampling.Direction(layout, 0, 0), first.DirectionRadius.xyz);
- Assert.Equal(8.4f, first.DirectionRadius.w);
- Assert.Equal(content.SampleSpacing, first.Sampling.x);
- Assert.Equal(content.ParentSampleSpacing, first.Sampling.y);
-
- var metadata = PlanetaryGpuPageBuilder.Metadata(content, 37, 0.25f);
- Assert.Equal(new float4((int)layout.Tile.Face, layout.Tile.Level, layout.Tile.X, layout.Tile.Y), metadata.Address);
- Assert.Equal(new float4(37, layout.StorageSize, layout.InteriorSize, layout.BorderSize), metadata.Layout);
- Assert.Equal(0.25f, metadata.State.y);
- }
-}
diff --git a/tests/CultMath.Tests/PlanetaryGpuPageTests.cs.meta b/tests/CultMath.Tests/PlanetaryGpuPageTests.cs.meta
deleted file mode 100644
index 00f4a3a..0000000
--- a/tests/CultMath.Tests/PlanetaryGpuPageTests.cs.meta
+++ /dev/null
@@ -1,2 +0,0 @@
-fileFormatVersion: 2
-guid: 87266638e8010f546ab6c430a712868b
\ No newline at end of file
diff --git a/tests/CultMath.Tests/PlanetaryMapTileEncodingTests.cs b/tests/CultMath.Tests/PlanetaryMapTileEncodingTests.cs
deleted file mode 100644
index 112cb31..0000000
--- a/tests/CultMath.Tests/PlanetaryMapTileEncodingTests.cs
+++ /dev/null
@@ -1,52 +0,0 @@
-using System.IO;
-using Xunit;
-
-namespace CultMath.Tests;
-
-public sealed class PlanetaryMapTileEncodingTests
-{
- [Fact]
- public void BinaryTileRoundTripsIdentityValidityAndSurfaceEvidence()
- {
- var field = PlanetaryFieldDefinition.Create(5, 10, 3, AdvancedErosionParameters.Default);
- var layout = new PlanetaryMapTileLayout(new(PlanetaryProjectionKind.EqualEarth), 1, 1, 0, 5, 1);
- var key = new PlanetaryMapTileKey(field.FieldVersion, 2, 17, layout, PlanetaryQueryScale.AtFootprint(0.25f, 0.1f));
- var tile = PlanetaryMapTileBaker.Bake(field, key, new FlatField());
- using var bytes = new MemoryStream();
- PlanetaryMapTileEncoding.Write(bytes, tile, leaveOpen: true);
- bytes.Position = 0;
- var decoded = PlanetaryMapTileEncoding.Read(bytes);
-
- Assert.Equal(tile.Key, decoded.Key);
- Assert.Equal(tile.Validity, decoded.Validity);
- for (var i = 0; i < tile.Samples.Length; i++)
- if (tile.Validity[i]) Assert.Equal(tile.Samples[i], decoded.Samples[i]);
- }
-
- [Fact]
- public void DecoderRejectsForeignMagicAndVersions()
- {
- Assert.Throws(() => PlanetaryMapTileEncoding.Read(new MemoryStream(new byte[] { 1, 2, 3, 4, 1, 0, 0, 0 })));
- var bytes = new byte[] { (byte)'C', (byte)'M', (byte)'P', (byte)'T', 99, 0, 0, 0 };
- Assert.Throws(() => PlanetaryMapTileEncoding.Read(new MemoryStream(bytes)));
- }
-
- [Fact]
- public void DecoderRejectsTrailingBytes()
- {
- var field = PlanetaryFieldDefinition.Create(2, 1, 0, AdvancedErosionParameters.Default);
- var layout = new PlanetaryMapTileLayout(new(PlanetaryProjectionKind.Equirectangular), 0, 0, 0, 2, 0);
- var key = new PlanetaryMapTileKey(field.FieldVersion, 1, 0, layout, PlanetaryQueryScale.AtFootprint(1));
- var tile = PlanetaryMapTileBaker.Bake(field, key, new FlatField());
- using var bytes = new MemoryStream();
- PlanetaryMapTileEncoding.Write(bytes, tile, leaveOpen: true);
- bytes.WriteByte(0xff);
- bytes.Position = 0;
- Assert.Throws(() => PlanetaryMapTileEncoding.Read(bytes));
- }
-
- private readonly struct FlatField : IPlanetaryBaseField
- {
- public PlanetaryBaseFieldSample Sample(float3 direction) => new(0, float3.zero, 0, float3.zero, 0);
- }
-}
diff --git a/tests/CultMath.Tests/PlanetaryMapTileEncodingTests.cs.meta b/tests/CultMath.Tests/PlanetaryMapTileEncodingTests.cs.meta
deleted file mode 100644
index 027754e..0000000
--- a/tests/CultMath.Tests/PlanetaryMapTileEncodingTests.cs.meta
+++ /dev/null
@@ -1,2 +0,0 @@
-fileFormatVersion: 2
-guid: e916ef83478d48b43ac99777f5e0f731
\ No newline at end of file
diff --git a/tests/CultMath.Tests/PlanetaryPageTests.cs b/tests/CultMath.Tests/PlanetaryPageTests.cs
deleted file mode 100644
index c4e4045..0000000
--- a/tests/CultMath.Tests/PlanetaryPageTests.cs
+++ /dev/null
@@ -1,57 +0,0 @@
-using Xunit;
-
-namespace CultMath.Tests;
-
-public sealed class PlanetaryPageTests
-{
- [Fact]
- public void BilinearSamplingAndSummaryUseOnePageContract()
- {
- var layout = new PlanetaryPageLayout(new(PlanetaryCubeFace.PositiveZ, 0, 0, 0), 3, 1);
- var samples = new PlanetaryPageSample[layout.StorageSize * layout.StorageSize];
- for (var y = 0; y < layout.StorageSize; y++)
- for (var x = 0; x < layout.StorageSize; x++)
- samples[y * layout.StorageSize + x] = new(x + y * 10, new float3(x, y, 0), x / 10.0f, y / 10.0f);
-
- var sample = PlanetaryPageSampling.Bilinear(samples, layout, new float2(0.25f, 0.75f));
- Assert.Equal(26.5f, sample.Height, 4);
- Assert.Equal(new float3(1.5f, 2.5f, 0), sample.TangentGradient);
- var summary = PlanetaryPageSampling.Summarize(samples, layout, 0.125f);
- Assert.Equal(0, summary.MinimumHeight);
- Assert.Equal(44, summary.MaximumHeight);
- Assert.Equal(0.125f, summary.UnresolvedHeightBound);
- Assert.True(summary.MaximumSlope > 0);
- }
-
- [Fact]
- public void ChildPageStoresOnlyResidualAndComposesBackToDirectField()
- {
- var field = new PlanetaryFieldDefinition(7, 8.4f, 3, AdvancedErosionParameters.Default with
- {
- Scale = 0.63f,
- Strength = 0.12f,
- Octaves = 7,
- });
- var source = new FixtureField();
- var root = PlanetaryPageBaker.Bake(field, new(new(PlanetaryCubeFace.PositiveZ, 0, 0, 0), 17, 2), source);
- var child = PlanetaryPageBaker.Bake(field, new(new(PlanetaryCubeFace.PositiveZ, 1, 1, 0), 17, 2), source);
- var direction = PlanetaryPageSampling.DirectionAtLocal(child.Layout, 0.5, 0.5);
- var pages = new[] { new PlanetaryResidentPage(root, 1), new PlanetaryResidentPage(child, 1) };
- Assert.True(PlanetaryPageSetSampling.TrySample(pages, direction, out var composed));
- var spacing = PlanetaryPageSampling.NominalAngularTexelSize(child.Layout) * field.Radius;
- var direct = PlanetaryField.Sample(field, direction, source.Sample(direction), PlanetaryQueryScale.AtFootprint(spacing));
- Assert.InRange(MathF.Abs(composed.Height - direct.RadialDisplacement), 0, 2.0e-5f);
- Assert.InRange(math.distance(composed.TangentGradient, direct.TangentGradient), 0, 3.0e-5f);
- }
-
- private readonly struct FixtureField : IPlanetaryBaseField
- {
- public PlanetaryBaseFieldSample Sample(float3 direction)
- {
- var value = direction.x * 0.4f - direction.y * 0.2f + direction.z * 0.5f;
- var gradient = new float3(0.4f, -0.2f, 0.5f);
- gradient -= direction * math.dot(gradient, direction);
- return new(value * 0.08f, gradient * 0.08f, value, gradient, value);
- }
- }
-}
diff --git a/tests/CultMath.Tests/PlanetaryPageTests.cs.meta b/tests/CultMath.Tests/PlanetaryPageTests.cs.meta
deleted file mode 100644
index 70a4386..0000000
--- a/tests/CultMath.Tests/PlanetaryPageTests.cs.meta
+++ /dev/null
@@ -1,2 +0,0 @@
-fileFormatVersion: 2
-guid: 7c3ae5598d3b9164a9bd3a736a22ba9a
\ No newline at end of file
diff --git a/tests/CultMath.Tests/PlanetaryPatchTests.cs b/tests/CultMath.Tests/PlanetaryPatchTests.cs
deleted file mode 100644
index 6564c77..0000000
--- a/tests/CultMath.Tests/PlanetaryPatchTests.cs
+++ /dev/null
@@ -1,40 +0,0 @@
-using Xunit;
-
-namespace CultMath.Tests;
-
-public sealed class PlanetaryPatchTests
-{
- [Fact]
- public void CubeSpherePatchMeshUsesCanonicalTopology()
- {
- var meshes = PlanetaryPatch.CreateCubeSphere(8);
- Assert.Equal(6, meshes.Length);
- foreach (var mesh in meshes)
- {
- Assert.Equal(81, mesh.Vertices.Length);
- Assert.Equal(8 * 8 * 6, mesh.Indices.Length);
- Assert.All(mesh.Vertices, vertex => Assert.Equal(1, math.length(vertex.UnitDirection), 5));
- foreach (var index in mesh.Indices) Assert.InRange(index, 0, mesh.Vertices.Length - 1);
- }
- }
-
- [Fact]
- public void NeighborFaceCornersAreSingleValued()
- {
- var directions = PlanetaryPatch.CreateCubeSphere(1)
- .SelectMany(mesh => mesh.Vertices.Select(vertex => vertex.UnitDirection))
- .ToArray();
- var groups = directions.GroupBy(direction => new
- {
- X = Math.Sign(direction.x),
- Y = Math.Sign(direction.y),
- Z = Math.Sign(direction.z),
- });
- Assert.Equal(8, groups.Count());
- Assert.All(groups, group =>
- {
- var first = group.First();
- Assert.All(group, direction => Assert.InRange(math.distance(first, direction), 0, 1.0e-6f));
- });
- }
-}
diff --git a/tests/CultMath.Tests/PlanetaryPatchTests.cs.meta b/tests/CultMath.Tests/PlanetaryPatchTests.cs.meta
deleted file mode 100644
index 8599047..0000000
--- a/tests/CultMath.Tests/PlanetaryPatchTests.cs.meta
+++ /dev/null
@@ -1,2 +0,0 @@
-fileFormatVersion: 2
-guid: 36840ef44326f84409f850163b00e1f7
\ No newline at end of file
diff --git a/tests/CultMath.Tests/PlanetaryProjectionTests.cs b/tests/CultMath.Tests/PlanetaryProjectionTests.cs
deleted file mode 100644
index ad811d6..0000000
--- a/tests/CultMath.Tests/PlanetaryProjectionTests.cs
+++ /dev/null
@@ -1,73 +0,0 @@
-using Xunit;
-
-namespace CultMath.Tests;
-
-public sealed class PlanetaryProjectionTests
-{
- [Theory]
- [InlineData(PlanetaryProjectionKind.Equirectangular)]
- [InlineData(PlanetaryProjectionKind.WebMercator)]
- [InlineData(PlanetaryProjectionKind.EqualEarth)]
- [InlineData(PlanetaryProjectionKind.Orthographic)]
- [InlineData(PlanetaryProjectionKind.AzimuthalEquidistant)]
- [InlineData(PlanetaryProjectionKind.AzimuthalEqualArea)]
- [InlineData(PlanetaryProjectionKind.LocalTangent)]
- public void ProjectionRoundTripsVisibleDirections(PlanetaryProjectionKind kind)
- {
- var parameters = new PlanetaryProjectionParameters(kind, 0.31, -0.22);
- foreach (var longitude in new[] { -0.4, -0.1, 0.2, 0.65 })
- foreach (var latitude in new[] { -0.6, -0.15, 0.3, 0.7 })
- {
- var direction = Direction(longitude + parameters.CenterLongitude, latitude);
- if (!PlanetaryProjection.TryForward(direction, parameters, out var map)) continue;
- Assert.True(PlanetaryProjection.TryInverse(map, parameters, out var roundTrip));
- Assert.InRange(math.distance(direction, roundTrip), 0, 3.0e-5f);
- }
- }
-
- [Fact]
- public void CubeAtlasRoundTripsEveryFaceInterior()
- {
- var projection = new PlanetaryProjectionParameters(PlanetaryProjectionKind.CubeAtlas);
- foreach (var face in Enum.GetValues())
- {
- var direction = PlanetaryTopology.Direction(new(face, 0.23, -0.41));
- Assert.True(PlanetaryProjection.TryForward(direction, projection, out var map));
- Assert.True(PlanetaryProjection.TryInverse(map, projection, out var roundTrip));
- Assert.InRange(math.distance(direction, roundTrip), 0, 2.0e-6f);
- }
- }
-
- [Fact]
- public void MercatorRejectsPolarSingularity()
- {
- var northPole = new float3(0, 0, 1);
- Assert.False(PlanetaryProjection.TryForward(northPole, new(PlanetaryProjectionKind.WebMercator), out _));
- }
-
- [Fact]
- public void MapTileBakerCarriesFieldIdentityAndProjectionValidity()
- {
- var field = new PlanetaryFieldDefinition(99, 10, 0, AdvancedErosionParameters.Default);
- var layout = new PlanetaryMapTileLayout(
- new(PlanetaryProjectionKind.Orthographic), 0, 0, 0, 9, 1);
- var key = new PlanetaryMapTileKey(field.FieldVersion, 1, 7, layout, PlanetaryQueryScale.AtFootprint(0.1f));
- var tile = PlanetaryMapTileBaker.Bake(field, key, new FlatField());
- Assert.Equal(key, tile.Key);
- Assert.Equal(layout.StorageSize * layout.StorageSize, tile.Samples.Length);
- Assert.Contains(tile.Validity, valid => valid);
- Assert.Contains(tile.Validity, valid => !valid);
- Assert.All(tile.Samples.Where((_, index) => tile.Validity[index]), sample => Assert.Equal(field.FieldVersion, sample.FieldVersion));
- }
-
- private static float3 Direction(double longitude, double latitude)
- {
- var cos = Math.Cos(latitude);
- return new((float)(cos * Math.Cos(longitude)), (float)(cos * Math.Sin(longitude)), (float)Math.Sin(latitude));
- }
-
- private readonly struct FlatField : IPlanetaryBaseField
- {
- public PlanetaryBaseFieldSample Sample(float3 unitDirection) => new(0, float3.zero, 0, float3.zero, 0);
- }
-}
diff --git a/tests/CultMath.Tests/PlanetaryProjectionTests.cs.meta b/tests/CultMath.Tests/PlanetaryProjectionTests.cs.meta
deleted file mode 100644
index 7f37f78..0000000
--- a/tests/CultMath.Tests/PlanetaryProjectionTests.cs.meta
+++ /dev/null
@@ -1,2 +0,0 @@
-fileFormatVersion: 2
-guid: f370036bb1663524fb4a4d47dfd50401
\ No newline at end of file
diff --git a/tests/CultMath.Tests/PlanetaryQueryTests.cs b/tests/CultMath.Tests/PlanetaryQueryTests.cs
deleted file mode 100644
index 48d865f..0000000
--- a/tests/CultMath.Tests/PlanetaryQueryTests.cs
+++ /dev/null
@@ -1,46 +0,0 @@
-using Xunit;
-
-namespace CultMath.Tests;
-
-public sealed class PlanetaryQueryTests
-{
- private static readonly PlanetaryFieldDefinition Field = new(12, 10, 0, AdvancedErosionParameters.Default with { Strength = 0, Octaves = 0 });
- private static readonly PlanetaryQueryScale Scale = PlanetaryQueryScale.AtFootprint(0.1f, 0);
-
- [Fact]
- public void RayIntersectionAndClearanceUseTheCpuSurfaceContract()
- {
- var source = new BulgedSphere();
- Assert.True(PlanetaryQueries.TryIntersectRay(
- Field, new double3(0, 0, 20), new double3(0, 0, -1), 30,
- Scale, new PlanetaryRadialBounds(-1, 1), source, out var hit));
- Assert.InRange(hit.Distance, 8.99999, 9.00001);
- Assert.InRange(hit.PlanetLocalPosition.z, 10.99999, 11.00001);
- var clearance = PlanetaryQueries.SegmentClearance(
- Field, new double3(-2, 0, 12), new double3(2, 0, 12), 0.1, Scale, source);
- Assert.True(clearance > 0.8);
- }
-
- [Fact]
- public void GreatCircleAndRegionQueriesReturnVersionedSamples()
- {
- var source = new BulgedSphere();
- var path = PlanetaryQueries.SampleGreatCircle(Field, new float3(1, 0, 0), new float3(0, 1, 0), 8, Scale, source);
- Assert.Equal(9, path.Length);
- Assert.Equal(MathF.PI * 5, path[^1].Distance, 4);
- Assert.All(path, point => Assert.Equal(Field.FieldVersion, point.Surface.FieldVersion));
- var summary = PlanetaryQueries.Summarize(Field, path.Select(point => point.Surface.UnitDirection).ToArray(), Scale, source);
- Assert.True(summary.MaximumDisplacement >= summary.MinimumDisplacement);
- Assert.True(summary.MaximumSlope >= 0);
- }
-
- private readonly struct BulgedSphere : IPlanetaryBaseField
- {
- public PlanetaryBaseFieldSample Sample(float3 direction)
- {
- var displacement = direction.z;
- var gradient = new float3(0, 0, 1) - direction * direction.z;
- return new(displacement, gradient, 0, float3.zero, 0);
- }
- }
-}
diff --git a/tests/CultMath.Tests/PlanetaryQueryTests.cs.meta b/tests/CultMath.Tests/PlanetaryQueryTests.cs.meta
deleted file mode 100644
index d72b2cb..0000000
--- a/tests/CultMath.Tests/PlanetaryQueryTests.cs.meta
+++ /dev/null
@@ -1,2 +0,0 @@
-fileFormatVersion: 2
-guid: 93cf8771fdd520b41a4e931a6566a80a
\ No newline at end of file
diff --git a/tests/CultMath.Tests/PlanetaryRadialRefinementTests.cs b/tests/CultMath.Tests/PlanetaryRadialRefinementTests.cs
deleted file mode 100644
index d0263b6..0000000
--- a/tests/CultMath.Tests/PlanetaryRadialRefinementTests.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-using Xunit;
-
-namespace CultMath.Tests;
-
-public sealed class PlanetaryRadialRefinementTests
-{
- [Fact]
- public void FourBoundedStepsConvergeFromTheRasterCaptureShell()
- {
- var center=new float3(0.3f,-0.2f,0.7f);
- var radial=math.normalize(new float3(0.2f,-0.4f,0.9f));
- var targetRadius=3.85f;
- var target=center+radial*targetRadius;
- var origin=target+math.normalize(radial+new float3(0.12f,-0.05f,0.02f))*6.0f;
- var position=center+radial*(targetRadius+0.30f);
- var ray=math.normalize(position-origin);
- for(var step=0;step<4;step++)
- position=PlanetaryRadialRefinement.Step(position,ray,center,targetRadius);
- Assert.InRange(math.abs(math.length(position-center)-targetRadius),0.0f,1.0e-4f);
- }
-
- [Fact]
- public void StepClampsWeakDerivativeCorrections()
- {
- var position=new float3(0,0,4.1f);
- var result=PlanetaryRadialRefinement.Step(position,new float3(1,0,0),float3.zero,3.8f);
- Assert.InRange(math.length(result-position),0.07999f,0.08001f);
- }
-}
diff --git a/tests/CultMath.Tests/PlanetaryRadialRefinementTests.cs.meta b/tests/CultMath.Tests/PlanetaryRadialRefinementTests.cs.meta
deleted file mode 100644
index b9f3bea..0000000
--- a/tests/CultMath.Tests/PlanetaryRadialRefinementTests.cs.meta
+++ /dev/null
@@ -1,2 +0,0 @@
-fileFormatVersion: 2
-guid: d34c0018ee2b700448b16540eca8ebcf
\ No newline at end of file
diff --git a/tests/CultMath.Tests/PlanetaryResidencyTests.cs b/tests/CultMath.Tests/PlanetaryResidencyTests.cs
deleted file mode 100644
index d2b89b7..0000000
--- a/tests/CultMath.Tests/PlanetaryResidencyTests.cs
+++ /dev/null
@@ -1,57 +0,0 @@
-using Xunit;
-
-namespace CultMath.Tests;
-
-public sealed class PlanetaryResidencyTests
-{
- private static readonly PlanetaryFieldDefinition Field = new(
- 1, 8.4f, 0, AdvancedErosionParameters.Default with { Scale = 0.63f, Strength = 0.12f, Octaves = 7 });
- private static readonly PlanetaryLodParameters Lod = new(5, 17, 2, 720, 8.4f / 65536);
-
- [Fact]
- public void SelectorReturnsSixRootsAndOneAncestorChain()
- {
- var direction = math.normalize(new float3(-0.2f, 0.91f, 0.37f));
- var selected = PlanetaryLodSelector.SelectAncestorChain(Field, direction, 8.45f, Lod);
- Assert.Equal(6, selected.Count(tile => tile.Level == 0));
- var descendants = selected.Where(tile => tile.Level > 0).OrderBy(tile => tile.Level).ToArray();
- for (var i = 1; i < descendants.Length; i++) Assert.Equal(descendants[i - 1], descendants[i].Parent());
- if (descendants.Length > 0) Assert.Equal(PlanetaryTopology.TileAt(direction, descendants[^1].Level), descendants[^1]);
- }
-
- [Fact]
- public void ResidencyFadesResidualsWithoutChangingContentDuringPresentation()
- {
- var root = new PlanetaryTileAddress(PlanetaryCubeFace.PositiveX, 0, 0, 0);
- var child = root.Child(1, 0);
- var residency = new PlanetaryResidualResidency();
- var arrival = residency.Update(new[] { root, child }, 0, 1);
- var midpoint = residency.Update(new[] { root, child }, 0.5f, 1);
- var settled = residency.Update(new[] { root, child }, 1, 1);
- Assert.Equal(arrival.ContentVersion, midpoint.ContentVersion);
- Assert.Equal(midpoint.ContentVersion, settled.ContentVersion);
- Assert.Equal(0, arrival.Tiles.Single(tile => tile.Tile == child).Blend);
- Assert.Equal(0.5f, midpoint.Tiles.Single(tile => tile.Tile == child).Blend);
- Assert.Equal(1, settled.Tiles.Single(tile => tile.Tile == child).Blend);
-
- var leaving = residency.Update(new[] { root }, 1.25f, 1);
- Assert.True(leaving.Tiles.Single(tile => tile.Tile == child).Departing);
- Assert.Equal(1, leaving.Tiles.Single(tile => tile.Tile == child).Blend);
- var gone = residency.Update(new[] { root }, 2.25f, 1);
- Assert.DoesNotContain(gone.Tiles, tile => tile.Tile == child);
- }
-
- [Fact]
- public void ReversingAnArrivalDepartsFromItsCurrentWeightWithoutJumping()
- {
- var root = new PlanetaryTileAddress(PlanetaryCubeFace.PositiveX, 0, 0, 0);
- var child = root.Child(0, 1);
- var residency = new PlanetaryResidualResidency();
- _ = residency.Update(new[] { root, child }, 0, 1);
- var arriving = residency.Update(new[] { root, child }, 0.25f, 1);
- var departing = residency.Update(new[] { root }, 0.25f, 1);
- Assert.Equal(0.25f, arriving.Tiles.Single(tile => tile.Tile == child).Blend);
- Assert.Equal(0.25f, departing.Tiles.Single(tile => tile.Tile == child).Blend);
- Assert.True(departing.Tiles.Single(tile => tile.Tile == child).Departing);
- }
-}
diff --git a/tests/CultMath.Tests/PlanetaryResidencyTests.cs.meta b/tests/CultMath.Tests/PlanetaryResidencyTests.cs.meta
deleted file mode 100644
index be7de2b..0000000
--- a/tests/CultMath.Tests/PlanetaryResidencyTests.cs.meta
+++ /dev/null
@@ -1,2 +0,0 @@
-fileFormatVersion: 2
-guid: 9ece3b02dbd3d7448baabee7f1fba0ab
\ No newline at end of file
diff --git a/tests/CultMath.Tests/PlanetaryTopologyTests.cs b/tests/CultMath.Tests/PlanetaryTopologyTests.cs
deleted file mode 100644
index 759928d..0000000
--- a/tests/CultMath.Tests/PlanetaryTopologyTests.cs
+++ /dev/null
@@ -1,63 +0,0 @@
-using Xunit;
-
-namespace CultMath.Tests;
-
-public sealed class PlanetaryTopologyTests
-{
- [Fact]
- public void FaceDirectionRoundTripsAcrossEveryFace()
- {
- foreach (var face in Enum.GetValues())
- foreach (var u in new[] { -0.9, -0.25, 0.0, 0.4, 0.9 })
- foreach (var v in new[] { -0.85, -0.1, 0.3, 0.88 })
- {
- var direction = PlanetaryTopology.Direction(new(face, u, v));
- var roundTrip = PlanetaryTopology.FaceCoordinate(direction);
- Assert.Equal(face, roundTrip.Face);
- Assert.Equal(u, roundTrip.U, 5);
- Assert.Equal(v, roundTrip.V, 5);
- }
- }
-
- [Fact]
- public void SiblingPagesShareBitStableBoundaryDirections()
- {
- var left = new PlanetaryPageLayout(new(PlanetaryCubeFace.PositiveZ, 1, 0, 0), 17, 2);
- var right = new PlanetaryPageLayout(new(PlanetaryCubeFace.PositiveZ, 1, 1, 0), 17, 2);
- for (var y = 0; y < left.InteriorSize; y++)
- {
- var a = PlanetaryPageSampling.Direction(left, left.BorderSize + left.InteriorSize - 1, left.BorderSize + y);
- var b = PlanetaryPageSampling.Direction(right, right.BorderSize, right.BorderSize + y);
- Assert.Equal(a, b);
- }
- }
-
- [Fact]
- public void DirectionSelectsContainingTileAndLocalCoordinate()
- {
- var expected = new PlanetaryTileAddress(PlanetaryCubeFace.NegativeY, 5, 19, 7);
- var direction = PlanetaryPageSampling.DirectionAtLocal(new(expected, 17, 2), 0.37, 0.61);
- Assert.Equal(expected, PlanetaryTopology.TileAt(direction, expected.Level));
- Assert.True(PlanetaryTopology.TryLocalCoordinate(direction, expected, out var local));
- Assert.Equal(0.37f, local.x, 4);
- Assert.Equal(0.61f, local.y, 4);
- }
-
- [Fact]
- public void BorderSamplesDoNotRequireNeighborResidency()
- {
- var page = new PlanetaryPageLayout(new(PlanetaryCubeFace.PositiveX, 0, 0, 0), 17, 3);
- var edge = PlanetaryPageSampling.Direction(page, page.BorderSize + page.InteriorSize - 1, page.BorderSize + 8);
- var border = PlanetaryPageSampling.Direction(page, page.StorageSize - 1, page.BorderSize + 8);
- Assert.NotEqual(edge, border);
- Assert.Equal(1, math.length(border), 5);
- }
-
- [Fact]
- public void NormalizedCubeBaselineSharesFaceEdges()
- {
- var a = PlanetaryTopology.NormalizedCubeDirection(new(PlanetaryCubeFace.PositiveZ, 1, -0.4));
- var b = PlanetaryTopology.NormalizedCubeDirection(new(PlanetaryCubeFace.PositiveX, -1, -0.4));
- Assert.InRange(math.distance(a, b), 0, 1.0e-6f);
- }
-}
diff --git a/tests/CultMath.Tests/PlanetaryTopologyTests.cs.meta b/tests/CultMath.Tests/PlanetaryTopologyTests.cs.meta
deleted file mode 100644
index b693014..0000000
--- a/tests/CultMath.Tests/PlanetaryTopologyTests.cs.meta
+++ /dev/null
@@ -1,2 +0,0 @@
-fileFormatVersion: 2
-guid: be3b0ec07fe65344db889d1e98448113
\ No newline at end of file
diff --git a/tests/CultMath.Tests/SphericalErosionTests.cs b/tests/CultMath.Tests/SphericalErosionTests.cs
deleted file mode 100644
index f634ba6..0000000
--- a/tests/CultMath.Tests/SphericalErosionTests.cs
+++ /dev/null
@@ -1,34 +0,0 @@
-using Xunit;
-
-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 repeated = 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, repeated);
- Assert.InRange(math.abs(a.HeightOffset-b.HeightOffset),0.0f,1.0e-6f);
- Assert.InRange(math.abs(a.Ridge-b.Ridge),0.0f,1.0e-6f);
- Assert.InRange(math.abs(a.Gully-b.Gully),0.0f,5.0e-5f);
- Assert.InRange(math.length(a.Gradient-b.Gradient),0.0f,5.0e-6f);
- 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);
- }
-}
diff --git a/tests/CultMath.Tests/SphericalErosionTests.cs.meta b/tests/CultMath.Tests/SphericalErosionTests.cs.meta
deleted file mode 100644
index 00ff1de..0000000
--- a/tests/CultMath.Tests/SphericalErosionTests.cs.meta
+++ /dev/null
@@ -1,2 +0,0 @@
-fileFormatVersion: 2
-guid: 60eb9e9e9f8ae2948b13403f5080168e
\ No newline at end of file
diff --git a/tools/CultMath.PlanetaryTileFixture.meta b/tools/CultMath.PlanetaryTileFixture.meta
deleted file mode 100644
index 7f40b6a..0000000
--- a/tools/CultMath.PlanetaryTileFixture.meta
+++ /dev/null
@@ -1,8 +0,0 @@
-fileFormatVersion: 2
-guid: 7197a2202273a9445b974bdd69109b1a
-folderAsset: yes
-DefaultImporter:
- externalObjects: {}
- userData:
- assetBundleName:
- assetBundleVariant:
diff --git a/unity/Runtime/PlanetaryPageUpload.cs b/unity/Runtime/PlanetaryPageUpload.cs
deleted file mode 100644
index 1a61a70..0000000
--- a/unity/Runtime/PlanetaryPageUpload.cs
+++ /dev/null
@@ -1,95 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Runtime.InteropServices;
-using UnityEngine;
-
-namespace CultMath.Unity;
-
-[StructLayout(LayoutKind.Sequential)]
-public readonly struct PlanetaryPageInputGpu
-{
- public PlanetaryPageInputGpu(Vector4 directionRadius, Vector4 sampling)
- {
- DirectionRadius = directionRadius;
- Sampling = sampling;
- }
-
- public readonly Vector4 DirectionRadius;
- public readonly Vector4 Sampling;
-}
-
-[StructLayout(LayoutKind.Sequential)]
-public readonly struct PlanetaryPageMetadataGpu
-{
- public PlanetaryPageMetadataGpu(Vector4 address, Vector4 layout, Vector4 bounds, Vector4 state)
- {
- Address = address;
- Layout = layout;
- Bounds = bounds;
- State = state;
- }
-
- public readonly Vector4 Address;
- public readonly Vector4 Layout;
- public readonly Vector4 Bounds;
- public readonly Vector4 State;
-}
-
-public readonly struct PlanetaryPageUpload
-{
- public PlanetaryPageUpload(
- PlanetaryPageInputGpu[] inputs,
- PlanetaryPageMetadataGpu[] metadata,
- int outputSampleCount,
- ulong contentVersion,
- ulong presentationVersion)
- {
- Inputs = inputs;
- Metadata = metadata;
- OutputSampleCount = outputSampleCount;
- ContentVersion = contentVersion;
- PresentationVersion = presentationVersion;
- }
-
- public PlanetaryPageInputGpu[] Inputs { get; }
- public PlanetaryPageMetadataGpu[] Metadata { get; }
- public int OutputSampleCount { get; }
- public ulong ContentVersion { get; }
- public ulong PresentationVersion { get; }
-}
-
-public static class PlanetaryPageUploadBuilder
-{
- public static PlanetaryPageUpload Build(
- in PlanetaryResidencySnapshot snapshot,
- int interiorSize,
- int borderSize,
- float radius)
- {
- if (!float.IsFinite(radius) || radius <= 0) throw new ArgumentOutOfRangeException(nameof(radius));
- var inputs = new List();
- var metadata = new PlanetaryPageMetadataGpu[snapshot.Tiles.Length];
- foreach (var (resident, pageIndex) in snapshot.Tiles.Select((resident, index) => (resident, index)))
- {
- var layout = new PlanetaryPageLayout(resident.Tile, interiorSize, borderSize).Validate();
- var content = PlanetaryGpuPageBuilder.BuildContent(layout, radius);
- var outputOffset = inputs.Count;
- foreach (var input in content.Inputs)
- {
- inputs.Add(new(
- ToUnity(input.DirectionRadius),
- ToUnity(input.Sampling)));
- }
- var commonMetadata = PlanetaryGpuPageBuilder.Metadata(content, outputOffset, resident.Blend);
- metadata[pageIndex] = new(
- ToUnity(commonMetadata.Address),
- ToUnity(commonMetadata.Layout),
- ToUnity(commonMetadata.Bounds),
- ToUnity(commonMetadata.State));
- }
- return new(inputs.ToArray(), metadata, inputs.Count, snapshot.ContentVersion, snapshot.PresentationVersion);
- }
-
- private static Vector4 ToUnity(float4 value) => new(value.x, value.y, value.z, value.w);
-}
diff --git a/unity/Runtime/PlanetaryPageUpload.cs.meta b/unity/Runtime/PlanetaryPageUpload.cs.meta
deleted file mode 100644
index 87bcb61..0000000
--- a/unity/Runtime/PlanetaryPageUpload.cs.meta
+++ /dev/null
@@ -1,2 +0,0 @@
-fileFormatVersion: 2
-guid: 7edd3bb2edb2e6448a2d54fc082c27dc
\ No newline at end of file
diff --git a/unity/Runtime/PlanetaryPatchMeshAdapter.cs b/unity/Runtime/PlanetaryPatchMeshAdapter.cs
deleted file mode 100644
index 69a72a2..0000000
--- a/unity/Runtime/PlanetaryPatchMeshAdapter.cs
+++ /dev/null
@@ -1,36 +0,0 @@
-using System;
-using UnityEngine;
-using UnityEngine.Rendering;
-
-namespace CultMath.Unity;
-
-public static class PlanetaryPatchMeshAdapter
-{
- public static Mesh CreateFaceMesh(PlanetaryCubeFace face, int cellsPerAxis, float radius = 1)
- {
- if (!float.IsFinite(radius) || radius <= 0) throw new ArgumentOutOfRangeException(nameof(radius));
- var source = PlanetaryPatch.CreateFace(face, cellsPerAxis);
- var vertices = new Vector3[source.Vertices.Length];
- var normals = new Vector3[source.Vertices.Length];
- var uv = new Vector2[source.Vertices.Length];
- for (var i = 0; i < source.Vertices.Length; i++)
- {
- var direction = source.Vertices[i].UnitDirection;
- var normal = new Vector3(direction.x, direction.y, direction.z);
- vertices[i] = normal * radius;
- normals[i] = normal;
- uv[i] = new Vector2(source.Vertices[i].LocalCoordinate.x, source.Vertices[i].LocalCoordinate.y);
- }
- var mesh = new Mesh
- {
- name = $"CultMath Planetary {face} {cellsPerAxis}x{cellsPerAxis}",
- indexFormat = vertices.Length > ushort.MaxValue ? IndexFormat.UInt32 : IndexFormat.UInt16,
- vertices = vertices,
- normals = normals,
- uv = uv,
- triangles = source.Indices,
- };
- mesh.RecalculateBounds();
- return mesh;
- }
-}
diff --git a/unity/Runtime/PlanetaryPatchMeshAdapter.cs.meta b/unity/Runtime/PlanetaryPatchMeshAdapter.cs.meta
deleted file mode 100644
index 69af0ab..0000000
--- a/unity/Runtime/PlanetaryPatchMeshAdapter.cs.meta
+++ /dev/null
@@ -1,2 +0,0 @@
-fileFormatVersion: 2
-guid: 9ad7b581e14ef774d8435069bb77ca6a
\ No newline at end of file
diff --git a/web/planetary-tile.cross-runtime.test.mjs b/web/planetary-tile.cross-runtime.test.mjs
deleted file mode 100644
index 2b45721..0000000
--- a/web/planetary-tile.cross-runtime.test.mjs
+++ /dev/null
@@ -1,21 +0,0 @@
-import assert from "node:assert/strict";
-import { readFile } from "node:fs/promises";
-import { decodePlanetarySurfaceTile } from "./planetary-tile.ts";
-
-if (process.argv.length !== 3) throw new Error("Expected a generated CMPT fixture path");
-const bytes = await readFile(process.argv[2]);
-const arrayBuffer = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
-const tile = decodePlanetarySurfaceTile(arrayBuffer);
-assert.equal(tile.header.projectionVersion, 3);
-assert.equal(tile.header.layerId, 29);
-assert.equal(tile.header.projectionKind, 2);
-assert.equal(tile.header.interiorSize, 5);
-assert.equal(tile.header.borderSize, 1);
-assert.equal(tile.storageSize, 7);
-assert.equal(tile.samples.length, 49);
-assert.ok(tile.samples.some(sample => sample !== null));
-for (const sample of tile.samples) {
- if (sample === null) continue;
- assert.ok(Number.isFinite(sample.radialDisplacement));
- assert.ok(Number.isFinite(sample.unresolvedHeightBound));
-}
diff --git a/web/planetary-tile.cross-runtime.test.mjs.meta b/web/planetary-tile.cross-runtime.test.mjs.meta
deleted file mode 100644
index 3c38da5..0000000
--- a/web/planetary-tile.cross-runtime.test.mjs.meta
+++ /dev/null
@@ -1,7 +0,0 @@
-fileFormatVersion: 2
-guid: 7bc397e5660d2b6448919c41983d35e8
-DefaultImporter:
- externalObjects: {}
- userData:
- assetBundleName:
- assetBundleVariant:
diff --git a/web/planetary-tile.test.mjs b/web/planetary-tile.test.mjs
deleted file mode 100644
index cb054f2..0000000
--- a/web/planetary-tile.test.mjs
+++ /dev/null
@@ -1,39 +0,0 @@
-import assert from "node:assert/strict";
-import { decodePlanetarySurfaceTile } from "./planetary-tile.ts";
-
-const bytes = new ArrayBuffer(152);
-const view = new DataView(bytes);
-let offset = 0;
-const u8 = value => view.setUint8(offset++, value);
-const u32 = value => { view.setUint32(offset, value, true); offset += 4; };
-const i32 = value => { view.setInt32(offset, value, true); offset += 4; };
-const u64 = value => { view.setBigUint64(offset, value, true); offset += 8; };
-const f32 = value => { view.setFloat32(offset, value, true); offset += 4; };
-const f64 = value => { view.setFloat64(offset, value, true); offset += 8; };
-
-for (const character of "CMPT") u8(character.charCodeAt(0));
-u32(1); u64(0x1234n); u32(2); u32(17); i32(0);
-f64(0); f64(0); f64(1);
-i32(0); i32(0); i32(0); i32(2); i32(0);
-f32(10); f32(0.5); i32(4);
-u8(1);
-for (const value of [1, 0, 0]) f32(value);
-f32(8.4); f32(0.25);
-for (const value of [0, 0.1, 0]) f32(value);
-for (const value of [0.99, -0.1, 0]) f32(value);
-for (const value of [0.1, 0.8, 0.2, 2.5, 0.05]) f32(value);
-u8(0); u8(0); u8(0);
-assert.equal(offset, bytes.byteLength);
-
-const tile = decodePlanetarySurfaceTile(bytes);
-assert.equal(tile.header.fieldVersion, 0x1234n);
-assert.equal(tile.header.layerId, 17);
-assert.equal(tile.storageSize, 2);
-assert.equal(tile.samples.length, 4);
-assert.equal(tile.samples[0]?.radialDisplacement, 0.25);
-assert.equal(tile.samples[1], null);
-
-assert.throws(() => decodePlanetarySurfaceTile(bytes.slice(0, bytes.byteLength - 1)), /Truncated/);
-const trailing = new Uint8Array(bytes.byteLength + 1);
-trailing.set(new Uint8Array(bytes));
-assert.throws(() => decodePlanetarySurfaceTile(trailing.buffer), /trailing/);
diff --git a/web/planetary-tile.test.mjs.meta b/web/planetary-tile.test.mjs.meta
deleted file mode 100644
index d622095..0000000
--- a/web/planetary-tile.test.mjs.meta
+++ /dev/null
@@ -1,7 +0,0 @@
-fileFormatVersion: 2
-guid: 04c93ecab90d465499aa3d60722e6fc3
-DefaultImporter:
- externalObjects: {}
- userData:
- assetBundleName:
- assetBundleVariant:
diff --git a/web/planetary-tile.ts b/web/planetary-tile.ts
deleted file mode 100644
index e68d70f..0000000
--- a/web/planetary-tile.ts
+++ /dev/null
@@ -1,89 +0,0 @@
-export const CULTMATH_PLANETARY_TILE_VERSION = 1;
-
-export interface PlanetaryTileHeader {
- fieldVersion: bigint;
- projectionVersion: number;
- layerId: number;
- projectionKind: number;
- centerLongitude: number;
- centerLatitude: number;
- projectionScale: number;
- level: number;
- x: number;
- y: number;
- interiorSize: number;
- borderSize: number;
- footprintMeters: number;
- maximumUnresolvedHeight: number;
-}
-
-export interface PlanetaryTileSample {
- direction: readonly [number, number, number];
- radius: number;
- radialDisplacement: number;
- tangentGradient: readonly [number, number, number];
- surfaceNormal: readonly [number, number, number];
- slope: number;
- ridge: number;
- gully: number;
- finestResolvedWavelength: number;
- unresolvedHeightBound: number;
-}
-
-export interface PlanetarySurfaceTile {
- header: PlanetaryTileHeader;
- storageSize: number;
- samples: readonly (PlanetaryTileSample | null)[];
-}
-
-export function decodePlanetarySurfaceTile(bytes: ArrayBuffer): PlanetarySurfaceTile {
- const view = new DataView(bytes);
- let offset = 0;
- const u8 = () => view.getUint8(offset++);
- const u32 = () => { const value = view.getUint32(offset, true); offset += 4; return value; };
- const i32 = () => { const value = view.getInt32(offset, true); offset += 4; return value; };
- const f32 = () => { const value = view.getFloat32(offset, true); offset += 4; return value; };
- const f64 = () => { const value = view.getFloat64(offset, true); offset += 8; return value; };
- const u64 = () => { const value = view.getBigUint64(offset, true); offset += 8; return value; };
- const vector3 = (): [number, number, number] => [f32(), f32(), f32()];
- const requireRemaining = (count: number) => {
- if (offset + count > view.byteLength) throw new Error("Truncated CultMath planetary tile");
- };
-
- requireRemaining(8);
- if (String.fromCharCode(u8(), u8(), u8(), u8()) !== "CMPT") throw new Error("Not a CultMath planetary tile");
- const version = u32();
- if (version !== CULTMATH_PLANETARY_TILE_VERSION) throw new Error(`Unsupported CultMath planetary tile version ${version}`);
- requireRemaining(76);
- const header: PlanetaryTileHeader = {
- fieldVersion: u64(),
- projectionVersion: u32(),
- layerId: u32(),
- projectionKind: i32(),
- centerLongitude: f64(),
- centerLatitude: f64(),
- projectionScale: f64(),
- level: i32(), x: i32(), y: i32(), interiorSize: i32(), borderSize: i32(),
- footprintMeters: f32(), maximumUnresolvedHeight: f32(),
- };
- const count = i32();
- const storageSize = header.interiorSize + header.borderSize * 2;
- if (count !== storageSize * storageSize) throw new Error("CultMath planetary tile sample count does not match layout");
- const samples: (PlanetaryTileSample | null)[] = new Array(count);
- for (let index = 0; index < count; index++) {
- requireRemaining(1);
- if (u8() === 0) { samples[index] = null; continue; }
- requireRemaining(64);
- samples[index] = {
- direction: vector3(),
- radius: f32(),
- radialDisplacement: f32(),
- tangentGradient: vector3(),
- surfaceNormal: vector3(),
- slope: f32(), ridge: f32(), gully: f32(),
- finestResolvedWavelength: f32(), unresolvedHeightBound: f32(),
- };
- }
- if (offset !== view.byteLength) throw new Error("CultMath planetary tile has trailing bytes");
- return { header, storageSize, samples };
-}
diff --git a/web/planetary-tile.ts.meta b/web/planetary-tile.ts.meta
deleted file mode 100644
index 337da3b..0000000
--- a/web/planetary-tile.ts.meta
+++ /dev/null
@@ -1,7 +0,0 @@
-fileFormatVersion: 2
-guid: f284e64628320844196b7aaea76475a4
-DefaultImporter:
- externalObjects: {}
- userData:
- assetBundleName:
- assetBundleVariant: