diff options
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 |
