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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
//! System that synchronises the engine camera to the Bevy camera entity.
//!
//! Bridges the engine's [`Camera`](rustial_engine::Camera) state to the
//! Bevy ECS every frame so the 3D scene matches the map view.
//!
//! ## What is synchronised
//!
//! | Engine field | Bevy component | Notes |
//! |-------------|----------------|-------|
//! | `eye_offset()` | `Transform` translation + `looking_at` | Camera-relative origin (see below) |
//! | `pitch` / `yaw` | `Transform` orientation (via up-vector) | Smooth blend avoids gimbal flip |
//! | `mode` | `Projection` (Perspective / Orthographic) | Near/far planes scaled by distance |
//! | `fov_y` | `PerspectiveProjection::fov` | Only in perspective mode |
//! | `distance` | `OrthographicProjection` half-height | Only in orthographic mode |
//!
//! ## Camera-relative origin
//!
//! Web Mercator world coordinates (meters east/north of the origin) can
//! exceed millions of meters. Storing these directly in Bevy's f32
//! `Transform` would cause severe jitter. Instead, the engine uses a
//! **camera-relative** model:
//!
//! - The Bevy camera is placed at `eye_offset()` (the orbital offset
//! from the target) and looks at `Vec3::ZERO`.
//! - All tile, terrain, vector, and model entities are positioned
//! relative to `camera.target_world()`, not in absolute world space.
//! - This keeps all f32 values small (within a few km of the origin),
//! eliminating jitter.
//!
//! ## Up-vector derivation
//!
//! The up-hint is computed from the orbital geometry in two regimes,
//! smoothly blended over a small pitch range:
//!
//! **Pitched** (`pitch > 0.15 rad`): the camera's "screen-right"
//! vector is the orbit-sphere tangent in the yaw direction:
//! `right = (cos(yaw), -sin(yaw), 0)`. The up-hint is then
//! `right x look` (normalised), which is always perpendicular to the!
//! look direction and always points "above the horizon".
//!
//! **Top-down** (`pitch <= 0.15 rad`): the look direction is nearly
//! `-Z`, making the horizontal right vector degenerate. Instead the
//! up-hint is `(sin(yaw), cos(yaw), 0)` so the yaw bearing controls
//! which map direction appears at the top of the screen.
//!
//! Key properties:
//!
//! - No north/south flip at any yaw (including yaw = PI).
//! - No gimbal-lock at any pitch.
//! - Smooth transition across the blend boundary.
//!
//! This mirrors the engine's
//! [`Camera::view_matrix`](rustial_engine::Camera::view_matrix) logic
//! exactly so the Bevy frustum matches the engine's CPU-side matrices.
//!
//! ## Projection sync
//!
//! Near and far clip planes are derived from `camera.distance` to
//! maximise depth-buffer precision:
//!
//! | Mode | Near | Far |
//! |------|------|-----|
//! | Perspective | `distance * 0.001` | `distance * 10 * pitch_factor` |
//! | Orthographic | `-distance * 100` | `+distance * 100` |
//!
//! `pitch_factor = min(1/cos(pitch), 100)` extends the far plane when
//! the camera pitches toward the horizon, preventing terrain and tiles
//! at the view's edge from being clipped.
//!
//! ## Scheduling
//!
//! Registered in [`PreUpdate`](bevy::prelude::PreUpdate) **after**
//! `update_map_state` so the camera reads the freshly ticked engine
//! state (post-animator, post-input).
use crateMapCamera;
use crateMapStateResource;
use *;
use CameraMode;
// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------
/// Minimum near-plane distance to prevent depth-buffer collapse when
/// `cam.distance()` is extremely small.
const MIN_NEAR_PLANE: f32 = 0.01;
// ---------------------------------------------------------------------------
// System
// ---------------------------------------------------------------------------
/// Sync the engine's [`Camera`](rustial_engine::Camera) to the Bevy
/// [`Camera3d`] + [`Transform`] + [`Projection`] each frame.
///
/// Operates on every entity carrying the [`MapCamera`] marker. In
/// practice there is exactly one, spawned by `setup_from_config`.
///
/// See the [module-level documentation](self) for the full description
/// of what is synchronised, the camera-relative origin pattern, and
/// the up-vector derivation.