diff options
| author | Somhairle H. Marisol <[email protected]> | 2026-09-29 16:17:02 +0800 |
|---|---|---|
| committer | Somhairle H. Marisol <[email protected]> | 2026-09-29 16:17:02 +0800 |
| commit | 012c66f7eef55e7ef98a3c09bba419b1b17c05ca (patch) | |
| tree | eaa3a0230e848d40d66418b3cfb29e898f7784ba /src/LivingVillage.Desktop/VillageArt.fs | |
| parent | c4917adfd5dec391196a399aa53d57c680e462f4 (diff) | |
| download | living-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.fs | 46 |
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 |
