summaryrefslogtreecommitdiff
path: root/src/LivingVillage.Desktop/SceneDetail.fs
blob: c51c47a5bcdfe4cd575628270dc84533112491eb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
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) ]