diff options
| author | Somhairle H. Marisol <[email protected]> | 2026-09-28 20:26:42 +0800 |
|---|---|---|
| committer | Somhairle H. Marisol <[email protected]> | 2026-09-28 20:26:42 +0800 |
| commit | 08925d4863d8aaf93a974b8fa21a91e931a0928e (patch) | |
| tree | f18055c232fc1a6022eeab552402185c6948ec02 /src/LivingVillage.Desktop/SceneDetail.fs | |
| parent | 2ab4b8a22aaec3482d62433cfa4b73a657f2a165 (diff) | |
| download | living-village-08925d4863d8aaf93a974b8fa21a91e931a0928e.tar.gz | |
p47: 场景细节升级 — 河道倒影(waterFrameTickAt微抖)+岸边芦苇加密度+鸭/叶漂浮元素+近景屋顶统一光源阴影
Diffstat (limited to 'src/LivingVillage.Desktop/SceneDetail.fs')
| -rw-r--r-- | src/LivingVillage.Desktop/SceneDetail.fs | 118 |
1 files changed, 118 insertions, 0 deletions
diff --git a/src/LivingVillage.Desktop/SceneDetail.fs b/src/LivingVillage.Desktop/SceneDetail.fs new file mode 100644 index 0000000..c51c47a --- /dev/null +++ b/src/LivingVillage.Desktop/SceneDetail.fs @@ -0,0 +1,118 @@ +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 + + // ---------------- 河道水面倒影 ---------------- + + let reflectionMaxAlpha = 128 + let reflectionRange = 3 + + /// 倒影 alpha 随离光源距离递减;到 0 即止,绝不留下硬块。 + let reflectionAlphaAt (distance: int) : int = + max 0 (reflectionMaxAlpha - distance * 34) + + /// 倒影微抖:复用 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 + + /// 岸上光源:桥头灯笼 + 主路石灯笼 + 门口红灯笼,去重后即倒影源。 + let lightSourceTiles (map: MapGen.Result) : (int * int) list = + (VillageArt.bridgeLanternTiles map + @ VillageArt.cc0LanternTiles map 12 + @ VillageArt.cc0RedLanternTiles map) + |> List.distinct + + /// 每个光源在其正下方连续水面格生成一条垂直倒影 (x, y, alpha)。 + let reflectionStreaks (map: MapGen.Result) : (int * int * int) list = + lightSourceTiles map + |> List.collect (fun (sx, sy) -> + [ for d in 1 .. reflectionRange do + let y = sy + d + if isWater map sx y then yield (sx, y, reflectionAlphaAt d) ]) + + // ---------------- 岸边植被加密度 ---------------- + + /// 在既有 `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) ] |
