bevy_basic_portals/portals/api.rs
1//! Components and structs to create portals without caring about their implementation
2
3use bevy_app::prelude::*;
4use bevy_camera::{primitives::HalfSpace, visibility::RenderLayers};
5use bevy_color::{Color, palettes::basic::GRAY};
6use bevy_ecs::prelude::*;
7use bevy_math::prelude::*;
8use bevy_mesh::Mesh3d;
9use bevy_reflect::Reflect;
10use bevy_render::render_resource::Face;
11use bevy_transform::prelude::*;
12
13use super::*;
14
15/// [Plugin] to add support for portals to a bevy App.
16pub struct PortalsPlugin {
17 /// If true, should check if any [PortalParts] entity despawned but still has a [PortalPart] referencing it with [check_portal_parts_back_references]
18 pub check_portal_parts_back_references: bool,
19 /// What to do when there is a problem getting a [PortalParts]
20 ///
21 /// Can happen when :
22 /// - a part (main camera, [Portal], [PortalDestination]) has despawned but the [PortalCamera] still exists,
23 /// - a part is missing a key component (see [CreatePortalParams], entities should be returned by the relevant queries).
24 /// - check_portal_camera_despawn is true and a portal camera has despawned or missing a key component but the [Portal] or [PortalDestination] still exist
25 ///
26 /// Defaults/`None` to despawn all entities and children with a warning, except for the main camera.
27 /// Will be added as a [Resource], can be changed during execution.
28 pub despawn_strategy: Option<PortalPartsDespawnStrategy>,
29}
30
31impl Default for PortalsPlugin {
32 fn default() -> Self {
33 PortalsPlugin {
34 check_portal_parts_back_references: true,
35 despawn_strategy: None,
36 }
37 }
38}
39
40impl PortalsPlugin {
41 pub const MINIMAL: Self = Self {
42 check_portal_parts_back_references: false,
43 despawn_strategy: Some(PortalPartsDespawnStrategy::PANIC),
44 };
45}
46
47impl Plugin for PortalsPlugin {
48 fn build(&self, app: &mut App) {
49 build_material(app);
50 build_create(app);
51 build_update(app);
52 build_despawn(
53 app,
54 self.despawn_strategy.clone(),
55 self.check_portal_parts_back_references,
56 );
57
58 #[cfg(feature = "picking_backend")]
59 app.add_plugins(crate::picking::PortalPickingBackendPlugin);
60 }
61}
62
63/// Strategy to despawn portal parts.
64///
65/// Defaults to despawn all parts with a warning (without their children), except for the main camera.
66#[derive(Resource, Clone, Reflect)]
67#[reflect(Resource)]
68pub struct PortalPartsDespawnStrategy {
69 pub main_camera: PortalPartDespawnStrategy,
70 pub portal: PortalPartDespawnStrategy,
71 pub destination: PortalPartDespawnStrategy,
72 pub portal_camera: PortalPartDespawnStrategy,
73}
74
75impl Default for PortalPartsDespawnStrategy {
76 fn default() -> Self {
77 PortalPartsDespawnStrategy::DESPAWN_AND_WARN
78 }
79}
80
81impl PortalPartsDespawnStrategy {
82 pub const DESPAWN_AND_WARN: Self = Self {
83 main_camera: PortalPartDespawnStrategy::Leave,
84 portal: PortalPartDespawnStrategy::WarnThenDespawnEntity,
85 destination: PortalPartDespawnStrategy::WarnThenDespawnEntity,
86 portal_camera: PortalPartDespawnStrategy::WarnThenDespawnEntity,
87 };
88
89 pub const PANIC: Self = Self {
90 main_camera: PortalPartDespawnStrategy::Leave,
91 portal: PortalPartDespawnStrategy::Panic,
92 destination: PortalPartDespawnStrategy::Panic,
93 portal_camera: PortalPartDespawnStrategy::Panic,
94 };
95
96 pub const DESPAWN_SILENTLY: Self = Self {
97 main_camera: PortalPartDespawnStrategy::Leave,
98 portal: PortalPartDespawnStrategy::DespawnEntity,
99 destination: PortalPartDespawnStrategy::DespawnEntity,
100 portal_camera: PortalPartDespawnStrategy::DespawnEntity,
101 };
102
103 pub const DESPAWN_WITH_CHILDREN_SILENTLY: Self = Self {
104 main_camera: PortalPartDespawnStrategy::Leave,
105 portal: PortalPartDespawnStrategy::DespawnWithChildren,
106 destination: PortalPartDespawnStrategy::DespawnWithChildren,
107 portal_camera: PortalPartDespawnStrategy::DespawnWithChildren,
108 };
109}
110
111/// Strategy to despawn a portal part if it is not yet despawned
112#[derive(Default, PartialEq, Eq, Copy, Clone, Reflect)]
113pub enum PortalPartDespawnStrategy {
114 /// Despawn the entity and all of its children with a warning
115 WarnThenDespawnWithChildren,
116 /// Despawn the entity and all of its children
117 DespawnWithChildren,
118 /// Despawn only the entity with a warning
119 #[default]
120 WarnThenDespawnEntity,
121 /// Despawn only the entity
122 DespawnEntity,
123 /// Don't despawn
124 Leave,
125 /// Panic
126 Panic,
127}
128
129impl PortalPartDespawnStrategy {
130 pub(super) fn should_panic(&self) -> bool {
131 self == &Self::Panic
132 }
133
134 pub(super) fn should_despawn(&self) -> bool {
135 self != &Self::Leave && self != &Self::Panic
136 }
137
138 pub(super) fn should_despawn_children(&self) -> bool {
139 self == &Self::WarnThenDespawnWithChildren || self == &Self::DespawnWithChildren
140 }
141
142 pub(super) fn should_warn(&self) -> bool {
143 self == &Self::WarnThenDespawnWithChildren || self == &Self::WarnThenDespawnEntity
144 }
145}
146
147/// [Component] to create a [Portal] and everything needed to make it work.
148///
149/// The portal will be created with everything needed by a trigger after the insertion/spawning command is flushed, and this component will be removed.
150///
151/// Requires [Mesh3d] to define the mesh of the portal, and all its dependencies. Indirectly requires [Transform] to locate the portal.
152#[derive(Component, Clone)]
153#[require(Mesh3d)]
154pub struct CreatePortal {
155 /// Where the portal should lead to.
156 pub destination: PortalDestinationSource,
157 /// What technique to use to render the portal effect, and how to define the
158 /// frustum when applicable.
159 pub portal_mode: PortalMode,
160 /// The camera that will see this portal, defaults to the first camera found.
161 pub main_camera: Option<Entity>,
162 /// Whether to cull the “front”, “back” or neither side of a the portal mesh.
163 ///
164 /// If set to `None`, the two sides of the portal are visible and work as a portal.
165 /// Be sure to set an appropriate [PortalMode] so that the frustum isn't assuming you only see the back of your mesh.
166 ///
167 /// Defaults to `Some(Face::Back)`, see [StandardMaterial](bevy_pbr::StandardMaterial).
168 pub cull_mode: Option<Face>,
169 /// Render layer used by the [PortalCamera], and debug elements.
170 pub render_layer: RenderLayers,
171 /// Configures debug elements, defaults to None.
172 pub debug: Option<DebugPortal>,
173}
174
175impl Default for CreatePortal {
176 fn default() -> Self {
177 Self {
178 destination: PortalDestinationSource::Create(CreatePortalDestination::default()),
179 portal_mode: PortalMode::default(),
180 main_camera: None,
181 cull_mode: Some(Face::Back),
182 render_layer: RenderLayers::default(),
183 debug: None,
184 }
185 }
186}
187
188/// How to create the [PortalDestination].
189#[derive(Clone)]
190pub enum PortalDestinationSource {
191 /// Use an already existing entity.
192 Use(Entity),
193 /// Create a [PortalDestination] with the given configuration.
194 Create(CreatePortalDestination),
195 /// Create a [PortalDestination] to make a mirror.
196 ///
197 /// Will set the [PortalDestination] as a child of the [Portal] entity
198 CreateMirror,
199}
200
201/// [PortalDestination] to be created
202#[derive(Clone, Default)]
203pub struct CreatePortalDestination {
204 /// Where to create the destination of the portal
205 pub transform: Transform,
206 /// Entity to use as a parent of the [PortalDestination]
207 pub parent: Option<Entity>,
208 /// Mirrors the image seen through the portal, see [MirrorConfig].
209 pub mirror: Option<MirrorConfig>,
210 //TODO: pub spawn_as_children: something like an EntityCommand?
211}
212
213impl From<Transform> for CreatePortalDestination {
214 fn from(transform: Transform) -> Self {
215 Self {
216 transform,
217 ..Default::default()
218 }
219 }
220}
221
222/// Configuration of the mirror effect, used in [CreatePortalDestination].
223///
224/// When a mirror effect is applied through a portal:
225/// - the portal camera will be first placed in the position it would be if the portal was nor mirrored
226/// - its position, forward direction and up direction (but not the right one) are then mirrored relative to a mirrored defined in the destination's space by `origin` and `normal`.
227/// - the u and/or v coordinates of the texture are swapped in the portal shader
228///
229/// To mirror the image vertically, the normal of the mirror would be Dir3:X to mirror according to the plane YZ of the destination.
230/// Since the up position is mirrored but not the right one, you typically want to mirror only the u coordinate of the texture.
231#[derive(Clone)]
232pub struct MirrorConfig {
233 pub origin: Vec3,
234 pub normal: Dir3,
235 pub mirror_u: bool,
236 pub mirror_v: bool,
237}
238
239impl Default for MirrorConfig {
240 fn default() -> Self {
241 MirrorConfig {
242 origin: Vec3::ZERO,
243 normal: Dir3::X,
244 mirror_u: true,
245 mirror_v: false,
246 }
247 }
248}
249
250/// What technique to use to render the portal effect, and what entities are seen
251/// or not through it.
252#[derive(Clone)]
253pub enum PortalMode {
254 /// The portal effect will be rendered on a texture with the same size as
255 /// the main camera's viewport, and a shader will define the UV-mapping using
256 /// the portal viewed through the main camera as a mask.
257 ///
258 /// The frustum will simply be defined from the projection matrix, which means
259 /// everything between the portal camera and the destination will be seen through
260 /// the portal.
261 MaskedImageNoFrustum,
262 /// Same as [PortalMode::MaskedImageNoFrustum], but a frustum will be defined to hide
263 /// objects between the portal camera and the destination.
264 ///
265 /// The frustum uses a [HalfSpace] in destination local space (it later takes into account
266 /// the destination transform for calculations in global space) as a near plane.
267 ///
268 /// `None` will assume the `Plane` is `{p, p.z < 0}` in local space, it should
269 /// be the same as `Some(Vec3::NEG_Z.extend(0.))`.
270 ///
271 /// If the boolean is true, when the camera is inside the half-space its normal will be inverted.
272 /// This is needed when using [CreatePortal::cull_mode] set to `None`.
273 ///
274 /// Note that this will *replace* the near plane of the frustum defined from
275 /// the projection matrix, which means that some objects might be considered
276 /// for rendering when they shouldn't be (for example, when the camera's forward
277 /// is almost parallel to the plane, objects behind the camera but in front of
278 /// the plane will be considered).
279 MaskedImageHalfSpaceFrustum((Option<HalfSpace>, bool)),
280 /// Same as [PortalMode::MaskedImageNoFrustum], but a frustum will be defined to hide
281 /// objects between the portal camera and the destination.
282 ///
283 /// The frustum uses an origin and a distance in destination local space as a near plane.
284 /// The near plane will always be facing the portal camera, at a certain distance from the origin.
285 ///
286 /// This is useful for 3D portals (like crystal balls).
287 MaskedImageSphereHalfSpaceFrustum((Vec3, f32)),
288 //TODO
289 //MaskedImageRectangleFrustum(PortalRectangleView),
290 //MaskedImageSphereRectangleFrustum(_)
291 // A projection matrix will be defined to fit.
292 //FittingProjectionRectangle(PortalRectangleView)
293}
294
295impl Default for PortalMode {
296 fn default() -> Self {
297 PortalMode::MaskedImageHalfSpaceFrustum((None, false))
298 }
299}
300
301/*#[derive(Clone)]
302pub struct PortalRectangleView {
303 origin: Vec3,
304 normal: Vec3,
305 rectangle: Vec2,
306}*/
307
308/// Configuration of debug elements.
309#[derive(Clone)]
310pub struct DebugPortal {
311 /// Name of the portal, used in the debug window's title.
312 pub name: Option<String>,
313 /// Color used by debug elements, defaults to gray.
314 pub color: Color,
315 /// Can show a copy of what is rendered through the portal using a copy of the [PortalCamera] marked with [PortalDebugCamera].
316 pub show_portal_texture: DebugPortalTextureView,
317 /// If true, displays a small sphere at the destination.
318 pub show_destination_point: bool,
319 /// If true, displays a copy of the portal mesh at the destination.
320 pub show_portal_copy: bool,
321 /// If true, displays a small sphere at the [PortalCamera] position.
322 pub show_portal_camera_point: bool,
323}
324
325impl Default for DebugPortal {
326 fn default() -> Self {
327 DebugPortal {
328 name: Default::default(),
329 color: GRAY.into(),
330 show_portal_texture: DebugPortalTextureView::None,
331 show_destination_point: true,
332 show_portal_copy: true,
333 show_portal_camera_point: true,
334 }
335 }
336}
337
338/// Configuration of displaying a copy of what the portal camera renders.
339#[derive(Clone)]
340pub enum DebugPortalTextureView {
341 /// Don't display what the portal camera renders.
342 None,
343 /// Display what the portal camera renders in a separate window.
344 Window,
345 /// Display what the portal camera renders in a UI Node with a ratio of the window's resolution.
346 /// Needs the ui_debug feature.
347 #[cfg(feature = "debug_ui")]
348 Widget(f32),
349}