summaryrefslogtreecommitdiff
path: root/src/LivingVillage.Desktop/SceneDetail.fs
diff options
context:
space:
mode:
authorSomhairle H. Marisol <[email protected]>2026-09-28 20:26:42 +0800
committerSomhairle H. Marisol <[email protected]>2026-09-28 20:26:42 +0800
commit08925d4863d8aaf93a974b8fa21a91e931a0928e (patch)
treef18055c232fc1a6022eeab552402185c6948ec02 /src/LivingVillage.Desktop/SceneDetail.fs
parent2ab4b8a22aaec3482d62433cfa4b73a657f2a165 (diff)
downloadliving-village-08925d4863d8aaf93a974b8fa21a91e931a0928e.tar.gz
p47: 场景细节升级 — 河道倒影(waterFrameTickAt微抖)+岸边芦苇加密度+鸭/叶漂浮元素+近景屋顶统一光源阴影
Diffstat (limited to 'src/LivingVillage.Desktop/SceneDetail.fs')
-rw-r--r--src/LivingVillage.Desktop/SceneDetail.fs118
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) ]