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
///! Components and structs to create portals without caring about their implementation

use bevy_app::prelude::*;
use bevy_asset::Handle;
use bevy_ecs::prelude::*;
use bevy_reflect::Reflect;
use bevy_render::{
    prelude::*,
    render_resource::Face,
    view::RenderLayers,
    primitives::Plane,
};
use bevy_transform::prelude::*;

use super::*;

/// [Plugin] to add support for portals to a bevy App.
pub struct PortalsPlugin {
    /// Whether and when to check for entities with [CreatePortal] components to create a portal.
    /// 
    /// Defaults to [PortalsCheckMode::AlwaysCheck].
    pub check_create: PortalsCheckMode,
    /// If true, should check if a [PortalCamera] despawned or has the wrong components with [check_portal_camera_despawn]
    pub check_portal_camera_despawn: bool,
    /// What to do when there is a problem getting a [PortalParts]
    /// 
    /// Can happen when :
    /// - a part (main camera, [Portal], [PortalDestination]) has despawned but the [PortalCamera] still exists,
    /// - a part is missing a key component (see [CreatePortalParams], entities should be returned by the relevant queries).
    /// - check_portal_camera_despawn is true and a portal camera has despawned or missing a key component but the [Portal] or [PortalDestination] still exist
    /// 
    /// Defaults/`None` to despawn all entities and children with a warning, except for the main camera.
    /// Will be added as a [Resource], can be changed during execution.
    pub despawn_strategy: Option<PortalPartsDespawnStrategy>,
}

impl Default for PortalsPlugin {
    fn default() -> Self {
        PortalsPlugin {
            check_create: PortalsCheckMode::AlwaysCheck,
            check_portal_camera_despawn: true,
            despawn_strategy: None,
        }
    }
}

impl PortalsPlugin {
    pub const MINIMAL: Self = Self {
        check_create: PortalsCheckMode::CheckAfterStartup,
        check_portal_camera_despawn: false,
        despawn_strategy: Some(PortalPartsDespawnStrategy::PANIC),
    };
}

impl Plugin for PortalsPlugin {
    fn build(&self, app: &mut App) {
        build_material(app);
        build_projection(app);
        build_create(app, &self.check_create);
        build_update(app);
        build_despawn(app, self.despawn_strategy.clone(), self.check_portal_camera_despawn);
    }
}

/// Whether and when [PortalsPlugin] should check for entities with [CreatePortal] components to create a portal using [create_portals].
#[derive(PartialEq, Eq, Clone)]
pub enum PortalsCheckMode {
    /// Don't set up this check automatically with the plugin, set-up [create_portals] manually, or use [CreatePortalCommand].
    Manual,
    /// Set up the check during [StartupSet::PostStartup], after [TransformPropagate](bevy_transform::TransformSystem::TransformPropagate).
    CheckAfterStartup,
    /// Set up the check during [StartupSet::PostStartup] and [CoreSet::Last], after [TransformPropagate](bevy_transform::TransformSystem::TransformPropagate).
    AlwaysCheck
}

/// Strategy to despawn portal parts.
/// 
/// Defaults to despawn all parts with a warning (without their children), except for the main camera.
#[derive(Resource, Clone, Reflect)]
#[reflect(Resource)]
pub struct PortalPartsDespawnStrategy {
    pub main_camera: PortalPartDespawnStrategy,
    pub portal: PortalPartDespawnStrategy,
    pub destination: PortalPartDespawnStrategy,
    pub portal_camera: PortalPartDespawnStrategy,
}

impl Default for PortalPartsDespawnStrategy {
    fn default() -> Self {
        PortalPartsDespawnStrategy::DESPAWN_AND_WARN
    }
}

impl PortalPartsDespawnStrategy {
    pub const DESPAWN_AND_WARN: Self = Self {
        main_camera: PortalPartDespawnStrategy::Leave,
        portal: PortalPartDespawnStrategy::WarnThenDespawnEntity,
        destination: PortalPartDespawnStrategy::WarnThenDespawnEntity,
        portal_camera: PortalPartDespawnStrategy::WarnThenDespawnEntity,
    };

    pub const PANIC: Self = Self {
        main_camera: PortalPartDespawnStrategy::Leave,
        portal: PortalPartDespawnStrategy::Panic,
        destination: PortalPartDespawnStrategy::Panic,
        portal_camera: PortalPartDespawnStrategy::Panic,
    };

    pub const DESPAWN_SILENTLY: Self = Self {
        main_camera: PortalPartDespawnStrategy::Leave,
        portal: PortalPartDespawnStrategy::DespawnEntity,
        destination: PortalPartDespawnStrategy::DespawnEntity,
        portal_camera: PortalPartDespawnStrategy::DespawnEntity,
    };

    pub const DESPAWN_WITH_CHILDREN_SILENTLY: Self = Self {
        main_camera: PortalPartDespawnStrategy::Leave,
        portal: PortalPartDespawnStrategy::DespawnWithChildren,
        destination: PortalPartDespawnStrategy::DespawnWithChildren,
        portal_camera: PortalPartDespawnStrategy::DespawnWithChildren,
    };
}

/// Strategy to despawn a portal part if it is not yet despawned
#[derive(Default, PartialEq, Eq, Clone, Reflect)]
pub enum PortalPartDespawnStrategy {
    /// Despawn the entity and all of its children with a warning
    WarnThenDespawnWithChildren,
    /// Despawn the entity and all of its children
    DespawnWithChildren,
    /// Despawn only the entity with a warning
    #[default]
    WarnThenDespawnEntity,
    /// Despawn only the entity
    DespawnEntity,
    /// Don't despawn
    Leave,
    /// Panic
    Panic,
}

impl PortalPartDespawnStrategy {
    pub(super) fn should_panic(&self) -> bool {
        self == &Self::Panic
    }

    pub(super) fn should_despawn(&self) -> bool {
        self != &Self::Leave && self != &Self::Panic
    }

    pub(super) fn should_despawn_children(&self) -> bool {
        self == &Self::WarnThenDespawnWithChildren || self == &Self::DespawnWithChildren
    }

    pub(super) fn should_warn(&self) -> bool {
        self == &Self::WarnThenDespawnWithChildren || self == &Self::WarnThenDespawnEntity
    }
}

/// [Bundle] to create a portal with all the components needed.
#[derive(Bundle, Default)]
pub struct CreatePortalBundle {
    /// Mesh of the portal.
    pub mesh: Handle<Mesh>,
    /// Configuration of the portal.
    pub create_portal: CreatePortal,
    /// Transform of the portal.
    pub portal_transform: Transform,
    pub global_transform: GlobalTransform,
    pub visibility: Visibility,
    pub computed_visibility: ComputedVisibility,
}

/// [Component] to create a [Portal] and everything needed to make it work.
/// 
/// The portal will be created after the next check (see [PortalsCheckMode]), if it has the other components in [CreatePortalBundle].
#[derive(Component, Clone)]
pub struct CreatePortal {
    /// Where the portal should lead to.
    pub destination: AsPortalDestination,
    /// What technique to use to render the portal effect, and how to define the
    /// frustum when applicable.
    pub portal_mode: PortalMode,
    /// The camera that will see this portal, defaults to the first camera found.
    pub main_camera: Option<Entity>,
    /// Whether to cull the “front”, “back” or neither side of a the portal mesh.
    /// 
    /// If set to `None`, the two sides of the portal are visible and work as a portal.
    /// 
    /// Defaults to `Some(Face::Back)`, see [StandardMaterial](bevy_pbr::StandardMaterial).
    pub cull_mode: Option<Face>,
    /// Render layer used by the [PortalCamera], and debug elements.
    pub render_layer: RenderLayers,
    /// Configures debug elements, defaults to None.
    pub debug: Option<DebugPortal>,
}

impl Default for CreatePortal {
    fn default() -> Self {
        Self {
            destination: AsPortalDestination::Create(CreatePortalDestination::default()),
            portal_mode: PortalMode::default(),
            main_camera: None,
            cull_mode: Some(Face::Back),
            render_layer: RenderLayers::default(),
            debug: None,
        }
    }
}

/// How to create the [PortalDestination].
#[derive(Clone)]
pub enum AsPortalDestination {
    /// Use an already existing entity.
    Use(Entity),
    /// Create a [PortalDestination] with the given configuration.
    Create(CreatePortalDestination),
    /// Create a [PortalDestination] to make a mirror.
    /// 
    /// Will set the [PortalDestination] as a child of the [Portal] entity
    CreateMirror
}

/// [PortalDestination] to be created
#[derive(Clone, Default)]
pub struct CreatePortalDestination {
    /// Where to create the destination of the portal
    pub transform: Transform,
    ///Entity to use as a parent of the [PortalDestination]
    pub parent: Option<Entity>,
    //TODO: pub spawn_as_children: something like an EntityCommand?
}

/// What technique to use to render the portal effect, and what entities are seen
/// or not through it.
#[derive(Clone)]
pub enum PortalMode {
    /// The portal effect will be rendered on a texture with the same size as
    /// the main camera's viewport, and a shader will define the UV-mapping using
    /// the portal viewed through the main camera as a mask.
    /// 
    /// The frustum will simply be defined from the projection matrix, which means
    /// everything between the portal camera and the destination will be seen through
    /// the portal
    MaskedImageNoFrustum,
    /// Same as [PortalMode::MaskedImageNoFrustum], but a frustum will be defined,
    /// using a [Plane] in the mesh/entity local space (it later takes into account
    /// the destination transform for calculations in global space).
    /// 
    /// `None` will assume the `Plane` is `{p, p.z < 0}` in local space, it should
    /// be the same as `Some(Vec3::NEG_Z.extend(0.))`.
    /// 
    /// Note that this will *replace* the near plane of the frustum defined from
    /// the projection matrix, which means that some objects might be considered
    /// for rendering when they shouldn't be (for example, when the camera's forward
    /// is almost parallel to the plane, objects behind the camera but in front of
    /// the plane will be considered).
    MaskedImagePlaneFrustum(Option<Plane>),
    //TODO
    //MaskedImageRectangleFrustum(PortalRectangleView),
    //MaskedImageSpherePlaneFrustum(_)
    //MaskedImageSphereRectangleFrustum(_)
    // A projection matrix will be defined to fit.
    //FittingProjectionRectangle(PortalRectangleView)
}

impl Default for PortalMode {
    fn default() -> Self {
        PortalMode::MaskedImagePlaneFrustum(None)
    }
}

/*#[derive(Clone)]
pub struct PortalRectangleView {
    origin: Vec3,
    normal: Vec3,
    rectangle: Vec2,
}*/

/// Configuration of debug elements.
#[derive(Clone)]
pub struct DebugPortal {
    /// Name of the portal, used in the debug window's title.
    pub name: Option<String>,
    /// Color used by debug elements, defaults to gray.
    pub color: Color,
    /// If true, shows a debug window, it will use a copy of the [PortalCamera] marked with [PortalDebugCamera].
    pub show_window: bool,
    /// If true, displays a small sphere at the destination.
    pub show_destination_point: bool,
    /// If true, displays a copy of the portal mesh at the destination.
    pub show_portal_copy: bool,
    /// If true, displays a small sphere at the [PortalCamera] position.
    pub show_portal_camera_point: bool
}

impl Default for DebugPortal {
    fn default() -> Self {
        DebugPortal {
            name: Default::default(),
            color: Color::GRAY,
            show_window: true,
            show_destination_point: true,
            show_portal_copy: true,
            show_portal_camera_point: true
        }
    }
}