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
//! A production-ready camera controller for 3D editors; intended for anyone who needs to rapidly
//! and intuitively navigate virtual spaces.
//!
//! Camera controllers are very subjective! As someone who has spent years using camera controllers
//! in mechanical engineering CAD software, I've developed my own opinions about what matters in a
//! camera controller. This is my attempt to make the controller I've always wanted, that fixes the
//! annoyances I've encountered.
//!
//! *Because* camera controllers are so subjective, I felt the need to write out the impetus for
//! making this thing, what matters to me, and how I decided between conflicting goals. Somehow,
//! this ended up as a manifesto of sorts. If you came here to learn how to use or extend this
//! plugin, I've boiled the manifesto down into two sentences:
//!
//! > A camera controller needs to be responsive, robust, and satisfying to use. When there is
//! conflict between these needs, they should be prioritized in that order.
//!
//! Now that you've absorbed my wisdom, feel free to skip ahead to the [Usage](crate#usage) section.
//!
//! Or don't. It's up to you.
//!
//! # Philosophy
//!
//! These are the properties of a good editor camera controller, in order of importance. These are
//! the driving values for the choices I've made here. You might disagree and have different values
//! or priorities!
//!
//! ## Responsive
//!
//! A good camera controller should never feel floaty or disconnected. It should go exactly where
//! the user commands it to go. Responsiveness isn't simply "low latency", it's about respecting the
//! user's intent.
//!
//! #### First-order input
//!
//! The most precise inputs are first-order, that is, controlling the position of something
//! directly, instead of its velocity (second-order) or acceleration (third-order). An example of
//! this is using a mouse vs. a gamepad for controlling the rotation of a first person view. The
//! mouse is first order, the position of the mouse on the mousepad directly corresponds with the
//! direction the player is facing. Conversely, a joystick controls the velocity of the view
//! rotation. All that is to say, where possible, the camera controller should use pointer inputs
//! *directly*.
//!
//! #### Pixel-perfect panning
//!
//! When you click and drag to pan the scene, the thing you click on should stick to your pointer,
//! and never drift. This should hold true even if inputs are being smoothed.
//!
//! #### Intuitive zoom
//!
//! The camera should zoom in and out in the direction you are pointing. If the user is hovering
//! over something, the speed of the camera should automatically adjust to quickly zoom up to it
//! without clipping through it.
//!
//! #### Predictable rotation
//!
//! When you click and drag to orbit the scene in 3d, the center of rotation should be located where
//! your pointer was when the drag started.
//!
//! #### Intuitive perspective toggle
//!
//! Toggling between different fields of view, or between perspective and orthographic projections,
//! should not cause the camera view to jump or change suddenly. The view should smoothly warp,
//! keeping the last interacted point stationary on the screen.
//!
//! ## Robust
//!
//! A camera controller should work in any scenario, and handle failure gracefully and
//! unsurprisingly when inputs are ambiguous.
//!
//! #### Works in all conditions:
//!
//! All of features in the previous section should work regardless of framerate, distance, scale,
//! camera field of view, and camera projection - including orthographic.
//!
//! #### Graceful fallback
//!
//! if nothing is under the pointer when a camera motion starts, the last-known depth should be
//! used, to prevent erratic behavior when the hit test fails. If a user was orbiting around a point
//! on an object, then clicks to rotate about empty space, the camera should not shoot off into
//! space because nothing was under the cursor.
//!
//! ### Satisfying
//!
//! The controller should *feel* good to use.
//!
//! #### Momentum
//!
//! Panning and orbiting should support configurable momentum, to allow you to "flick" the camera
//! through the scene to cover distance and make the feel of the camera tunable. This is especially
//! useful for trackpad and touch users.
//!
//! #### Smoothness
//!
//! The smoothness of inputs should be configurable as a tradeoff between fluidity of motion and
//! responsiveness. This is particularly useful when showing the screen to other people, where fast
//! motions can be disorienting or even nauseating.
//!
//! # Usage
//!
//! This plugin only requires three things to work. The `bevy_mod_picking` plugin for hit tests, the
//! [`DefaultEditorCamPlugins`] plugin group, and the [`EditorCam`](crate::prelude::EditorCam)
//! component. Controller settings are configured per-camera in the
//! [`EditorCam`](crate::prelude::EditorCam) component.
//!
//! ## Getting Started
//!
//! #### 1. Add `bevy_mod_picking`
//!
//! The camera controller uses [`bevy_picking_core`] for pointer interactions. If you already use
//! the picking plugin, then using this camera controller is essentially free because it can reuse
//! those same hit tests you are already running.
//!
//! If you are not using the picking plugin yet, all you need to get started are the default
//! plugins:
//!
//! ```
//! # let mut app = bevy::app::App::new();
//! app.add_plugins(bevy_mod_picking::DefaultPickingPlugins);
//! ```
//!
//! #### 2. Add `DefaultEditorCamPlugins`
//!
//! This is a plugin group that adds the camera controller, as well as all the [extensions]. You can
//! instead add [`controller::MinimalEditorCamPlugin`], though you will need to add your own input
//! plugin if you do.
//!
//! ```
//! # let mut app = bevy::app::App::new();
//! app.add_plugins(bevy_editor_cam::DefaultEditorCamPlugins);
//! ```
//!
//! #### 3. Insert the `EditorCam` component
//!
//! Finally, insert [`controller::component::EditorCam`] onto any cameras that you want to control.
//! This marks the cameras as controllable and holds all camera controller settings.
//!
//! ```
//! # use bevy::ecs::system::Commands;
//! # use bevy_editor_cam::prelude::*;
//! # fn test(mut commands: Commands) {
//! commands.spawn((
//! // Camera
//! EditorCam::default(),
//! ));
//! # }
//! ```
//!
//! # Other notable features
//!
//! I've also implemented a few other features that are handy for a camera controller like this.
//!
//! ### Compatible with floating origins and other controllers
//!
//! This controller does all computations in view space. The result of this is that you can move the
//! camera wherever you want, update its transform, and it will continue to behave normally, as long
//! as the camera isn't being controlled by the user while you do this. This means you can control
//! this camera with another camera controller, or use it in a floating origin system.
//!
//! ### Independent skybox
//!
//! When working in a CAD context, it is common to use orthographic projections to remove
//! perspective distortion from the image. However, because an ortho projection has zero field of
//! view, the view of the skybox is infinitesimally small, i.e. only a single pixel of the skybox is
//! visible. To fix this, an [extension](extensions) is provided to attach a skybox to a camera that
//! is independent from that camera's field of view.
//!
//! ### Pointer and Hit Test Agnostic
//!
//! Users of this library shouldn't be forced into using any particular hit testing method, like CPU
//! raycasting. The controller uses [`bevy_picking_core`] to work with:
//!
//! - Arbitrary hit testing backends, including those written by users. See
//! [`bevy_picking_core::backend`] for more information.
//! - Any number of pointing inputs, including touch.
//! - Viewports and multi-pass rendering.
#![warn(missing_docs)]
pub mod controller;
pub mod extensions;
pub mod input;
/// Common imports.
pub mod prelude {
pub use crate::{
controller::{component::*, *},
DefaultEditorCamPlugins,
};
}
use bevy_app::{prelude::*, PluginGroupBuilder};
/// Adds [`bevy_editor_cam`](crate) functionality with all extensions and the default input plugin.
pub struct DefaultEditorCamPlugins;
impl PluginGroup for DefaultEditorCamPlugins {
#[allow(clippy::let_and_return)] // Needed for conditional compilation
fn build(self) -> PluginGroupBuilder {
let group = PluginGroupBuilder::start::<Self>()
.add(input::DefaultInputPlugin)
.add(controller::MinimalEditorCamPlugin)
.add(extensions::dolly_zoom::DollyZoomPlugin);
#[cfg(feature = "extension_anchor_indicator")]
let group = group.add(extensions::anchor_indicator::AnchorIndicatorPlugin);
#[cfg(feature = "extension_independent_skybox")]
let group = group.add(extensions::independent_skybox::IndependentSkyboxPlugin);
group
}
}