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) ]
|