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}