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
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
|
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) ]
|