forked from bevyengine/bevy
-
Notifications
You must be signed in to change notification settings - Fork 0
/
camera_sub_view.rs
321 lines (294 loc) · 10.7 KB
/
camera_sub_view.rs
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
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
//! Demonstrates different sub view effects.
//!
//! A sub view is essentially a smaller section of a larger viewport. Some use
//! cases include:
//! - Split one image across multiple cameras, for use in a multimonitor setups
//! - Magnify a section of the image, by rendering a small sub view in another
//! camera
//! - Rapidly change the sub view offset to get a screen shake effect
use bevy::{
prelude::*,
render::camera::{ScalingMode, SubCameraView, Viewport},
};
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Startup, setup)
.add_systems(Update, (move_camera_view, resize_viewports))
.run();
}
#[derive(Debug, Component)]
struct MovingCameraMarker;
/// Set up a simple 3D scene
fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
let transform = Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y);
// Plane
commands.spawn((
Mesh3d(meshes.add(Plane3d::default().mesh().size(5.0, 5.0))),
MeshMaterial3d(materials.add(Color::srgb(0.3, 0.5, 0.3))),
));
// Cube
commands.spawn((
Mesh3d(meshes.add(Cuboid::default())),
MeshMaterial3d(materials.add(Color::srgb(0.8, 0.7, 0.6))),
Transform::from_xyz(0.0, 0.5, 0.0),
));
// Light
commands.spawn((
PointLight {
shadows_enabled: true,
..default()
},
Transform::from_xyz(4.0, 8.0, 4.0),
));
// Main perspective camera:
//
// The main perspective image to use as a comparison for the sub views.
commands.spawn((
Camera3d::default(),
Camera::default(),
ExampleViewports::PerspectiveMain,
transform,
));
// Perspective camera right half:
//
// For this camera, the projection is perspective, and `size` is half the
// width of the `full_size`, while the x value of `offset` is set to half
// the value of the full width, causing the right half of the image to be
// shown. Since the viewport has an aspect ratio of 1x1 and the sub view has
// an aspect ratio of 1x2, the image appears stretched along the horizontal
// axis.
commands.spawn((
Camera3d::default(),
Camera {
sub_camera_view: Some(SubCameraView {
// The values of `full_size` and `size` do not have to be the
// exact values of your physical viewport. The important part is
// the ratio between them.
full_size: UVec2::new(10, 10),
// The `offset` is also relative to the values in `full_size`
// and `size`
offset: Vec2::new(5.0, 0.0),
size: UVec2::new(5, 10),
}),
order: 1,
..default()
},
ExampleViewports::PerspectiveStretched,
transform,
));
// Perspective camera moving:
//
// For this camera, the projection is perspective, and the offset is updated
// continuously in 150 units per second in `move_camera_view`. Since the
// `full_size` is 500x500, the image should appear to be moving across the
// full image once every 3.3 seconds. `size` is a fifth of the size of
// `full_size`, so the image will appear zoomed in.
commands.spawn((
Camera3d::default(),
Camera {
sub_camera_view: Some(SubCameraView {
full_size: UVec2::new(500, 500),
offset: Vec2::ZERO,
size: UVec2::new(100, 100),
}),
order: 2,
..default()
},
transform,
ExampleViewports::PerspectiveMoving,
MovingCameraMarker,
));
// Perspective camera different aspect ratio:
//
// For this camera, the projection is perspective, and the aspect ratio of
// the sub view (2x1) is different to the aspect ratio of the full view
// (2x2). The aspect ratio of the sub view matches the aspect ratio of
// the viewport and should show an unstretched image of the top half of the
// full perspective image.
commands.spawn((
Camera3d::default(),
Camera {
sub_camera_view: Some(SubCameraView {
full_size: UVec2::new(800, 800),
offset: Vec2::ZERO,
size: UVec2::new(800, 400),
}),
order: 3,
..default()
},
ExampleViewports::PerspectiveControl,
transform,
));
// Main orthographic camera:
//
// The main orthographic image to use as a comparison for the sub views.
commands.spawn((
Camera3d::default(),
Projection::from(OrthographicProjection {
scaling_mode: ScalingMode::FixedVertical {
viewport_height: 6.0,
},
..OrthographicProjection::default_3d()
}),
Camera {
order: 4,
..default()
},
ExampleViewports::OrthographicMain,
transform,
));
// Orthographic camera left half:
//
// For this camera, the projection is orthographic, and `size` is half the
// width of the `full_size`, causing the left half of the image to be shown.
// Since the viewport has an aspect ratio of 1x1 and the sub view has an
// aspect ratio of 1x2, the image appears stretched along the horizontal axis.
commands.spawn((
Camera3d::default(),
Projection::from(OrthographicProjection {
scaling_mode: ScalingMode::FixedVertical {
viewport_height: 6.0,
},
..OrthographicProjection::default_3d()
}),
Camera {
sub_camera_view: Some(SubCameraView {
full_size: UVec2::new(2, 2),
offset: Vec2::ZERO,
size: UVec2::new(1, 2),
}),
order: 5,
..default()
},
ExampleViewports::OrthographicStretched,
transform,
));
// Orthographic camera moving:
//
// For this camera, the projection is orthographic, and the offset is
// updated continuously in 150 units per second in `move_camera_view`. Since
// the `full_size` is 500x500, the image should appear to be moving across
// the full image once every 3.3 seconds. `size` is a fifth of the size of
// `full_size`, so the image will appear zoomed in.
commands.spawn((
Camera3d::default(),
Projection::from(OrthographicProjection {
scaling_mode: ScalingMode::FixedVertical {
viewport_height: 6.0,
},
..OrthographicProjection::default_3d()
}),
Camera {
sub_camera_view: Some(SubCameraView {
full_size: UVec2::new(500, 500),
offset: Vec2::ZERO,
size: UVec2::new(100, 100),
}),
order: 6,
..default()
},
transform,
ExampleViewports::OrthographicMoving,
MovingCameraMarker,
));
// Orthographic camera different aspect ratio:
//
// For this camera, the projection is orthographic, and the aspect ratio of
// the sub view (2x1) is different to the aspect ratio of the full view
// (2x2). The aspect ratio of the sub view matches the aspect ratio of
// the viewport and should show an unstretched image of the top half of the
// full orthographic image.
commands.spawn((
Camera3d::default(),
Projection::from(OrthographicProjection {
scaling_mode: ScalingMode::FixedVertical {
viewport_height: 6.0,
},
..OrthographicProjection::default_3d()
}),
Camera {
sub_camera_view: Some(SubCameraView {
full_size: UVec2::new(200, 200),
offset: Vec2::ZERO,
size: UVec2::new(200, 100),
}),
order: 7,
..default()
},
ExampleViewports::OrthographicControl,
transform,
));
}
fn move_camera_view(
mut movable_camera_query: Query<&mut Camera, With<MovingCameraMarker>>,
time: Res<Time>,
) {
for mut camera in movable_camera_query.iter_mut() {
if let Some(sub_view) = &mut camera.sub_camera_view {
sub_view.offset.x = (time.elapsed_secs() * 150.) % 450.0 - 50.0;
sub_view.offset.y = sub_view.offset.x;
}
}
}
// To ensure viewports remain the same at any window size
fn resize_viewports(
windows: Query<&Window, With<bevy::window::PrimaryWindow>>,
mut viewports: Query<(&mut Camera, &ExampleViewports)>,
) {
let Ok(window) = windows.get_single() else {
return;
};
let window_size = window.physical_size();
let small_height = window_size.y / 5;
let small_width = window_size.x / 8;
let large_height = small_height * 4;
let large_width = small_width * 4;
let large_size = UVec2::new(large_width, large_height);
// Enforce the aspect ratio of the small viewports to ensure the images
// appear unstretched
let small_dim = small_height.min(small_width);
let small_size = UVec2::new(small_dim, small_dim);
let small_wide_size = UVec2::new(small_dim * 2, small_dim);
for (mut camera, example_viewport) in viewports.iter_mut() {
if camera.viewport.is_none() {
camera.viewport = Some(Viewport::default());
};
let Some(viewport) = &mut camera.viewport else {
continue;
};
let (size, position) = match example_viewport {
ExampleViewports::PerspectiveMain => (large_size, UVec2::new(0, small_height)),
ExampleViewports::PerspectiveStretched => (small_size, UVec2::ZERO),
ExampleViewports::PerspectiveMoving => (small_size, UVec2::new(small_width, 0)),
ExampleViewports::PerspectiveControl => {
(small_wide_size, UVec2::new(small_width * 2, 0))
}
ExampleViewports::OrthographicMain => {
(large_size, UVec2::new(large_width, small_height))
}
ExampleViewports::OrthographicStretched => (small_size, UVec2::new(small_width * 4, 0)),
ExampleViewports::OrthographicMoving => (small_size, UVec2::new(small_width * 5, 0)),
ExampleViewports::OrthographicControl => {
(small_wide_size, UVec2::new(small_width * 6, 0))
}
};
viewport.physical_size = size;
viewport.physical_position = position;
}
}
#[derive(Component)]
enum ExampleViewports {
PerspectiveMain,
PerspectiveStretched,
PerspectiveMoving,
PerspectiveControl,
OrthographicMain,
OrthographicStretched,
OrthographicMoving,
OrthographicControl,
}