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
}
}
}