namespace LivingVillage.Desktop /// P47 场景细节升级(纯函数层):河道倒影、岸边植被加密度、漂浮元素、近景楼层层错落。 /// 全部为帧计数 / 地图的纯函数:同参数逐字节可复现,无时钟、无随机源。 module SceneDetail = open LivingVillage.Kernel let private positiveModulo64 (modulus: int64) (value: int64) : int64 = let remainder = value % modulus if remainder < 0L then remainder + modulus else remainder /// 统一光源:来自画面左上,所有阴影一律投向右下(方向统一,不随物体改变)。 let shadowOffsetPx : int * int = (3, 4) let isWater (map: MapGen.Result) (x: int) (y: int) : bool = x >= 0 && x < map.Width && y >= 0 && y < map.Height && map.Tiles.[y * map.Width + x] = int MapGen.GroundTile.Water /// 可倒影水面:河面(Water)与水田(PaddyField,同样走水面动画渲染)。 let isWaterSurface (map: MapGen.Result) (x: int) (y: int) : bool = x >= 0 && x < map.Width && y >= 0 && y < map.Height && (map.Tiles.[y * map.Width + x] = int MapGen.GroundTile.Water || map.Tiles.[y * map.Width + x] = int MapGen.GroundTile.PaddyField) // ---------------- 河道水面倒影 ---------------- let reflectionMaxAlpha = 200 let reflectionRange = 4 /// 倒影射线允许穿过的非水面格数(如灯笼所立的桥面/岸沿),使「桥下红灯笼」的倒影 /// 仍落在其正下方的河面,而不是被桥面格截断。 let reflectionGap = 3 /// 倒影 alpha 随离光源距离递减;最低仍保持 96,保证整条暖色光柱在蓝灰水面上 /// 始终可辨(不会淡到被水面波纹吞掉)。 let reflectionAlphaAt (distance: int) : int = max 96 (reflectionMaxAlpha - (distance - 1) * 26) /// 倒影微抖:复用 waterFrameTickAt 的 3 相位,映射到 -1 / 0 / +1 像素水平位移。 let reflectionWobbleAt (tick: int64) (x: int) (y: int) : int = match VillageArt.waterFrameTickAt tick x y with | 0 -> -1 | 1 -> 0 | _ -> 1 /// 光源类型:桥头灯笼 / 主路石灯笼 / 门口红灯笼。倒影按类型着色。 type LightKind = | BridgeLantern | RoadLantern | RedLantern /// 岸上光源(含类型),按位置去重。 let lightSources (map: MapGen.Result) : (int * int * LightKind) list = let bridge = VillageArt.bridgeLanternTiles map |> List.map (fun (x, y) -> x, y, BridgeLantern) let road = VillageArt.cc0LanternTiles map 12 |> List.map (fun (x, y) -> x, y, RoadLantern) let red = VillageArt.cc0RedLanternTiles map |> List.map (fun (x, y) -> x, y, RedLantern) (bridge @ road @ red) |> List.distinctBy (fun (x, y, _) -> x, y) let lightSourceTiles (map: MapGen.Result) : (int * int) list = lightSources map |> List.map (fun (x, y, _) -> x, y) /// 某个光源向下的倒影:先穿过至多 reflectionGap 格非水面(桥面/岸沿),落到正下方 /// 水面上,再沿连续水面画最多 reflectionRange 格;返回 (x, y, alpha, kind)。 let reflectionStreakFor (map: MapGen.Result) (sx: int) (sy: int) (kind: LightKind) : (int * int * int * LightKind) list = let mutable start = sy + 1 let mutable gap = 0 while gap <= reflectionGap && start < map.Height && not (isWaterSurface map sx start) do start <- start + 1 gap <- gap + 1 if start >= map.Height || gap > reflectionGap || not (isWaterSurface map sx start) then [] else [ for d in 0 .. reflectionRange - 1 do let y = start + d if isWaterSurface map sx y then yield (sx, y, reflectionAlphaAt (d + 1), kind) ] /// 光源正下方倒影的格数(用于挑选倒影长度足够的取证机位)。 let reflectionLength (map: MapGen.Result) (x: int) (sy: int) : int = lightSources map |> List.filter (fun (sx, syy, _) -> sx = x && syy = sy) |> List.tryHead |> Option.map (fun (sx, syy, kind) -> reflectionStreakFor map sx syy kind |> List.length) |> Option.defaultValue 0 /// 每个光源在其正下方连续水面格生成一条垂直倒影 (x, y, alpha, kind),最长 4 格。 let reflectionStreaks (map: MapGen.Result) : (int * int * int * LightKind) list = lightSources map |> List.collect (fun (sx, sy, kind) -> reflectionStreakFor map sx sy kind) // ---------------- 岸边植被加密度 ---------------- /// 在既有 `cc0ReedTiles(spacing 4)`(x%4=0 的贴水陆格)基础上补一层: /// x%4<>0、且四邻任一为水的陆格,按位置 hash 取一半。于是芦苇带更密, /// 又与既有芦苇错列不重叠。 let bankReedTiles (map: MapGen.Result) : (int * int) list = if map.Width <= 0 then [] else [ for y in 1 .. map.Height - 2 do for x in 0 .. map.Width - 1 do let nearWater = isWater map (x - 1) y || isWater map (x + 1) y || isWater map x (y - 1) || isWater map x (y + 1) if not (isWater map x y) && nearWater && x % 4 <> 0 && (x * 7 + y * 13) % 2 = 0 then yield (x, y) ] // ---------------- 漂浮元素(鸭子浮标 / 漂浮落叶) ---------------- type FloatKind = | DuckFloat | FallenLeaf let floatKindOf (x: int) (y: int) : FloatKind = if (x * 3 + y * 5) % 3 = 0 then DuckFloat else FallenLeaf /// 确定性落在开阔水面的漂浮元素(2 格连续水面、非桥面),数量即「元素数量指标」。 let floatingProps (map: MapGen.Result) : (int * int * FloatKind) list = if map.Width <= 4 then [] else let bridgeSet = map.Bridges |> List.map (fun (x, y) -> y * map.Width + x) |> Set.ofList let isBridge x y = Set.contains (y * map.Width + x) bridgeSet [ for y in 2 .. map.Height - 3 do for x in 2 .. map.Width - 3 do if isWater map x y && isWater map (x + 1) y && not (isBridge x y) && not (isBridge (x + 1) y) && (x * 11 + y * 17) % 23 = 0 then yield (x, y, floatKindOf x y) ] /// 按 tick 的慢漂移(像素):水平 ±2、垂直 ±1,纯函数、有界、逐帧连续。 let floatDrift (tick: int64) (seed: int) : int * int = let dx = int (positiveModulo64 5L (tick / 24L + int64 seed)) - 2 let dy = int (positiveModulo64 3L (tick / 40L + int64 (seed * 7 + 1))) - 1 (dx, dy) /// 该漂浮元素的帧(0/1),只随 tick 慢速切换,保证水面鸭/叶有轻微起伏。 let floatFrame (tick: int64) (seed: int) : int = int (positiveModulo64 2L (tick / 32L + int64 seed)) // ---------------- 近景楼层层错落(屏幕空间前景层) ---------------- /// 前景层屋顶分段:等宽 tile 分段、高度按确定性函数错落(0/8/16/24 px 四层), /// 并随 tick 极慢移动(视差)。返回 (x, y, width)。 let foregroundRooftops (viewportWidth: int) (viewportHeight: int) (tick: int64) : (int * int * int) list = let tile = Sim.tilePixels let count = max 1 (viewportWidth / tile + 2) let baseY = viewportHeight - 64 [ for i in 0 .. count - 1 do let x = (i - 1) * tile let stagger = ((i * 7 + int (tick / 120L)) % 4) * 8 yield (x, baseY - stagger, tile) ]