summaryrefslogtreecommitdiff
path: root/src/LivingVillage.Desktop/VillageArt.fs
diff options
context:
space:
mode:
authorSomhairle H. Marisol <[email protected]>2026-09-29 16:17:02 +0800
committerSomhairle H. Marisol <[email protected]>2026-09-29 16:17:02 +0800
commit012c66f7eef55e7ef98a3c09bba419b1b17c05ca (patch)
treeeaa3a0230e848d40d66418b3cfb29e898f7784ba /src/LivingVillage.Desktop/VillageArt.fs
parentc4917adfd5dec391196a399aa53d57c680e462f4 (diff)
downloadliving-village-012c66f7eef55e7ef98a3c09bba419b1b17c05ca.tar.gz
p79: 夜帧炊烟增强与光晕去叠
- 炊烟:夜帧烟柱4段向上堆叠(整格32px, alpha 180+75)+暖色外发光(r30)+中性柔光团(r12); 烟柱强光像素(夜-昼贡献>=80) 181->1171 (x6.5),峰值112->173;白天帧逐像素不变。 - 光晕去叠:VillageArt.haloRingLayers 每光源<=2层(内芯+空心外环),芯环间确定暗隙,半径4->3 tile, 峰值150->118;光晕重叠面积(夜-昼贡献>=60)中央区 15184->9633 (-36.6%),全屏 15477->9926 (-35.9%)。 - 回归 P79NightPolishTests(3);analyze-p78.py 复跑仍 PASS;正午帧与 P78 完全一致=白天零回归。 - Desktop 367/367, Kernel 120/120, digest 953775FA...F7F6C 不变
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