summaryrefslogtreecommitdiff
path: root/src/LivingVillage.Desktop/VillageArt.fs
diff options
context:
space:
mode:
Diffstat (limited to 'src/LivingVillage.Desktop/VillageArt.fs')
-rw-r--r--src/LivingVillage.Desktop/VillageArt.fs46
1 files changed, 46 insertions, 0 deletions
diff --git a/src/LivingVillage.Desktop/VillageArt.fs b/src/LivingVillage.Desktop/VillageArt.fs
index db594d2..c797270 100644
--- a/src/LivingVillage.Desktop/VillageArt.fs
+++ b/src/LivingVillage.Desktop/VillageArt.fs
@@ -538,6 +538,52 @@ module VillageArt =
for (x, y, width) in ellipseRuns centerX centerY ringRadius ringRadius do
yield (Microsoft.Xna.Framework.Rectangle(x, y, width, 1), glowColor) ]
+ /// P79:单光源夜景光晕(去叠版)——最多 2 层视觉层:内芯(2 圈递减填充)+ 外环(空心圆环)。
+ /// 芯与环之间留一圈确定暗隙(不被任何 rect 覆盖),于是相邻多光源不会叠成一整团;
+ /// 累计 alpha 仍以 peakAlpha 为峰,不因层数而加深。半径由调用方收紧。
+ let haloRingLayers
+ (centerX: int)
+ (centerY: int)
+ (radius: int)
+ (peakAlpha: int) : (Microsoft.Xna.Framework.Rectangle * Microsoft.Xna.Framework.Color) list =
+ if radius <= 3 || peakAlpha <= 0 then
+ []
+ else
+ let innerRadius = max 1 (radius * 2 / 5)
+ let gapRadius = max (innerRadius + 2) (radius * 3 / 5)
+ let outerRadius = radius
+ // 内芯:两圈递减填充,累计峰值 = peakAlpha,不再加深。
+ let coreTarget (ringRadius: int) =
+ int (Math.Round(float peakAlpha * float (lanternFalloff (float32 ringRadius) (float32 innerRadius)), MidpointRounding.AwayFromZero))
+ let mutable previous = 0
+ let innerLayers =
+ [ for ringRadius in [ innerRadius; max 1 (innerRadius / 2) ] do
+ let target = coreTarget ringRadius
+ let increment = max 0 (target - previous)
+ previous <- max previous target
+ if increment > 0 then
+ for (x, y, width) in ellipseRuns centerX centerY ringRadius ringRadius do
+ yield (Microsoft.Xna.Framework.Rectangle(x, y, width, 1), premultiply 255 214 150 increment) ]
+ // 外环:空心圆环(外半径 outerRadius、内孔 gapRadius),只画左右两段,与内芯之间留暗隙。
+ let ringColor = premultiply 255 214 150 (max 1 (peakAlpha / 2))
+ let ringLayers =
+ [ for y in centerY - outerRadius .. centerY + outerRadius do
+ let dy = y - centerY
+ let halfAt (r: int) =
+ let squared = r * r - dy * dy
+ if squared <= 0 then 0 else int (sqrt (float squared))
+ let outerHalf = halfAt outerRadius
+ let gapHalf = halfAt gapRadius
+ if outerHalf > 0 then
+ if gapHalf <= 0 then
+ yield (Microsoft.Xna.Framework.Rectangle(centerX - outerHalf, y, outerHalf * 2 + 1, 1), ringColor)
+ else
+ let leftWidth = outerHalf - gapHalf
+ if leftWidth > 0 then
+ yield (Microsoft.Xna.Framework.Rectangle(centerX - outerHalf, y, leftWidth, 1), ringColor)
+ yield (Microsoft.Xna.Framework.Rectangle(centerX + gapHalf + 1, y, outerHalf - gapHalf, 1), ringColor) ]
+ innerLayers @ ringLayers
+
/// Characters spawn at tile centres; push the sprite down half a tile so its feet rest
/// on the tile's ground line instead of floating at the centre.
let characterFeetOffset = Sim.tilePixels / 2