From e9caacd781d80401ec16f7e75d8a366c4836c4e6 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Tue, 16 Jun 2026 03:39:52 +0100 Subject: [PATCH] Use swept Bokusho contact capsules --- .../Render/BokushoBrushSimulation.cs | 17 +++++++++-------- .../Render/Shaders/D3D12BokushoBrush.hlsl | 17 +++++++++-------- .../BokushoBrushSimulationTests.cs | 4 ++-- 3 files changed, 20 insertions(+), 18 deletions(-) diff --git a/src/Aquarium.Engine.Contracts/Render/BokushoBrushSimulation.cs b/src/Aquarium.Engine.Contracts/Render/BokushoBrushSimulation.cs index 07cf3d2..49ec7fd 100644 --- a/src/Aquarium.Engine.Contracts/Render/BokushoBrushSimulation.cs +++ b/src/Aquarium.Engine.Contracts/Render/BokushoBrushSimulation.cs @@ -6,10 +6,9 @@ namespace Aquarium.Engine.Render; public static class BokushoBrushSimulation { public const float PageDensityScale = 65535.0f; - public const float PagePatchTransferGain = 0.0032f; - public const float PagePatchTangentRadiusScale = 0.26f; - public const float PagePatchSweepRadiusScale = 0.5f; - public const float PagePatchNormalRadiusScale = 0.20f; + public const float PagePatchTransferGain = 0.0044f; + public const float PagePatchTangentRadiusScale = 0.32f; + public const float PagePatchNormalRadiusScale = 0.32f; public const float PagePatchMinimumRadius = 0.001f; public const float PagePatchWorldQuantum = 1.0f / 1024.0f; public const float PagePatchInkQuantum = 1.0f / 2048.0f; @@ -667,9 +666,10 @@ public static class BokushoBrushSimulation var normal = new Vector2(-tangent.Y, tangent.X); var (patchTangentRadius, patchNormalRadius) = PagePatchRadii(tangentRadius, normalRadius, sweepLength); var center = (previousTip + tip) * 0.5f; + var halfSweepLength = sweepLength * 0.5f; var extents = new Vector2( - MathF.Abs(tangent.X) * patchTangentRadius + MathF.Abs(normal.X) * patchNormalRadius, - MathF.Abs(tangent.Y) * patchTangentRadius + MathF.Abs(normal.Y) * patchNormalRadius); + MathF.Abs(tangent.X) * (halfSweepLength + patchTangentRadius) + MathF.Abs(normal.X) * patchNormalRadius, + MathF.Abs(tangent.Y) * (halfSweepLength + patchTangentRadius) + MathF.Abs(normal.Y) * patchNormalRadius); var minWorld = center - extents; var maxWorld = center + extents; var minPixel = WorldToPixel(minWorld, width, height, viewCenter, viewRadius); @@ -687,7 +687,8 @@ public static class BokushoBrushSimulation var uvX = width <= 1 ? 0.0f : x / (float)(width - 1); var world = viewCenter + (new Vector2(uvX, uvY) * 2.0f - Vector2.One) * viewRadius; var local = world - center; - var tangentDistance = Vector2.Dot(local, tangent) / patchTangentRadius; + var axialExcess = MathF.Max(MathF.Abs(Vector2.Dot(local, tangent)) - halfSweepLength, 0.0f); + var tangentDistance = axialExcess / patchTangentRadius; var normalDistance = Vector2.Dot(local, normal) / patchNormalRadius; var ellipse = MathF.Sqrt(tangentDistance * tangentDistance + normalDistance * normalDistance); var coverage = QuantizePositive(SmoothStep(1.0f, 0.0f, ellipse), PagePatchCoverageQuantum); @@ -791,7 +792,7 @@ public static class BokushoBrushSimulation public static (float TangentRadius, float NormalRadius) PagePatchRadii(float tangentRadius, float normalRadius, float sweepLength) { return ( - MathF.Max(tangentRadius * PagePatchTangentRadiusScale + sweepLength * PagePatchSweepRadiusScale, PagePatchMinimumRadius), + MathF.Max(tangentRadius * PagePatchTangentRadiusScale, PagePatchMinimumRadius), MathF.Max(normalRadius * PagePatchNormalRadiusScale, PagePatchMinimumRadius)); } diff --git a/src/Aquarium.Engine/Render/Shaders/D3D12BokushoBrush.hlsl b/src/Aquarium.Engine/Render/Shaders/D3D12BokushoBrush.hlsl index 4ba8fc2..1f90c14 100644 --- a/src/Aquarium.Engine/Render/Shaders/D3D12BokushoBrush.hlsl +++ b/src/Aquarium.Engine/Render/Shaders/D3D12BokushoBrush.hlsl @@ -1,10 +1,9 @@ #include "CultMath/CultMath.hlsl" static const float BOKUSHO_PAGE_DENSITY_SCALE = 65535.0; -static const float BOKUSHO_PAGE_PATCH_TRANSFER_GAIN = 0.0032; -static const float BOKUSHO_PAGE_PATCH_TANGENT_RADIUS_SCALE = 0.26; -static const float BOKUSHO_PAGE_PATCH_SWEEP_RADIUS_SCALE = 0.5; -static const float BOKUSHO_PAGE_PATCH_NORMAL_RADIUS_SCALE = 0.20; +static const float BOKUSHO_PAGE_PATCH_TRANSFER_GAIN = 0.0044; +static const float BOKUSHO_PAGE_PATCH_TANGENT_RADIUS_SCALE = 0.32; +static const float BOKUSHO_PAGE_PATCH_NORMAL_RADIUS_SCALE = 0.32; static const float BOKUSHO_PAGE_PATCH_MINIMUM_RADIUS = 0.001; static const float BOKUSHO_PAGE_PATCH_WORLD_QUANTUM = 1.0 / 1024.0; static const float BOKUSHO_PAGE_PATCH_INK_QUANTUM = 1.0 / 2048.0; @@ -142,12 +141,13 @@ void DepositSweptPatch(float2 previousTip, float2 tip, float normalRadius, float float sweepLength = length(sweep); float2 tangent = sweepLength > 0.000001 ? sweep / sweepLength : float2(1.0, 0.0); float2 normal = float2(-tangent.y, tangent.x); - float patchTangentRadius = max(tangentRadius * BOKUSHO_PAGE_PATCH_TANGENT_RADIUS_SCALE + sweepLength * BOKUSHO_PAGE_PATCH_SWEEP_RADIUS_SCALE, BOKUSHO_PAGE_PATCH_MINIMUM_RADIUS); + float patchTangentRadius = max(tangentRadius * BOKUSHO_PAGE_PATCH_TANGENT_RADIUS_SCALE, BOKUSHO_PAGE_PATCH_MINIMUM_RADIUS); float patchNormalRadius = max(normalRadius * BOKUSHO_PAGE_PATCH_NORMAL_RADIUS_SCALE, BOKUSHO_PAGE_PATCH_MINIMUM_RADIUS); float2 center = (previousTip + tip) * 0.5; + float halfSweepLength = sweepLength * 0.5; float2 extents = float2( - abs(tangent.x) * patchTangentRadius + abs(normal.x) * patchNormalRadius, - abs(tangent.y) * patchTangentRadius + abs(normal.y) * patchNormalRadius); + abs(tangent.x) * (halfSweepLength + patchTangentRadius) + abs(normal.x) * patchNormalRadius, + abs(tangent.y) * (halfSweepLength + patchTangentRadius) + abs(normal.y) * patchNormalRadius); float pageSize = max(pageView.w, 1.0); float2 minUv = (center - extents - pageView.xy) / max(pageView.z, 0.001) * 0.5 + 0.5; float2 maxUv = (center + extents - pageView.xy) / max(pageView.z, 0.001) * 0.5 + 0.5; @@ -166,7 +166,8 @@ void DepositSweptPatch(float2 previousTip, float2 tip, float normalRadius, float float uvX = pageSize <= 1.0 ? 0.0 : (float)x / (pageSize - 1.0); float2 world = pageView.xy + (float2(uvX, uvY) * 2.0 - 1.0) * pageView.z; float2 local = world - center; - float tangentDistance = dot(local, tangent) / patchTangentRadius; + float axialExcess = max(abs(dot(local, tangent)) - halfSweepLength, 0.0); + float tangentDistance = axialExcess / patchTangentRadius; float normalDistance = dot(local, normal) / patchNormalRadius; float ellipse = sqrt(tangentDistance * tangentDistance + normalDistance * normalDistance); float coverage = QuantizePositive(cultmath_smoothstep(1.0, 0.0, ellipse), BOKUSHO_PAGE_PATCH_COVERAGE_QUANTUM); diff --git a/tests/Aquarium.Engine.Tests/BokushoBrushSimulationTests.cs b/tests/Aquarium.Engine.Tests/BokushoBrushSimulationTests.cs index b873416..35989e2 100644 --- a/tests/Aquarium.Engine.Tests/BokushoBrushSimulationTests.cs +++ b/tests/Aquarium.Engine.Tests/BokushoBrushSimulationTests.cs @@ -292,14 +292,14 @@ public sealed class BokushoBrushSimulationTests } [Fact] - public void PagePatchRadiiIncludeBristleFootprintAndSweepDistance() + public void PagePatchRadiiKeepSweepLengthOutOfBristleFootprint() { var (tangentRadius, normalRadius) = BokushoBrushSimulation.PagePatchRadii( tangentRadius: 0.42f, normalRadius: 0.35f, sweepLength: 0.18f); - Assert.Equal(0.42f * BokushoBrushSimulation.PagePatchTangentRadiusScale + 0.18f * BokushoBrushSimulation.PagePatchSweepRadiusScale, tangentRadius, precision: 6); + Assert.Equal(0.42f * BokushoBrushSimulation.PagePatchTangentRadiusScale, tangentRadius, precision: 6); Assert.Equal(0.35f * BokushoBrushSimulation.PagePatchNormalRadiusScale, normalRadius, precision: 6); var tiny = BokushoBrushSimulation.PagePatchRadii(0.0f, 0.0f, 0.0f);