use std::f32::consts::TAU;
use bevy::{
asset::AssetPlugin,
core_pipeline::bloom::BloomSettings,
input::mouse::{MouseMotion, MouseWheel},
prelude::*,
render::{camera::Projection, render_resource::TextureFormat},
window::{close_on_esc, WindowPlugin, WindowResolution},
};
use bevy_mod_paramap::*;
const NORMAL_MAP: &str = "earth/normal_map.jpg";
const HEIGHT_MAP: &str = "earth/elevation_surface.jpg";
const ROUGH_MAP: &str = "earth/metallic_roughness.png";
const ALBEDO_MAP: &str = "earth/base_color.jpg";
const EMI_MAP: &str = "earth/emissive.jpg";
fn main() {
let mut app = App::new();
app.add_plugins(
DefaultPlugins
.set(WindowPlugin {
primary_window: Some(Window {
title: "Earth parallax mapping example".into(),
resolution: WindowResolution::new(756., 574.),
fit_canvas_to_parent: true,
..default()
}),
..default()
})
.set(AssetPlugin {
watch_for_changes: !cfg!(target_arch = "wasm32"),
..default()
}),
)
.add_plugin(ParallaxMaterialPlugin)
.insert_resource(AmbientLight {
color: Color::BLACK,
brightness: 0.01,
})
.insert_resource(ClearColor(Color::BLACK))
.insert_resource(Normal(None))
.add_startup_system(setup)
.add_system(pan_orbit_camera)
.add_system(update_normal)
.add_system(spin)
.add_system(close_on_esc);
app.register_type::<Spin>();
app.run();
}
#[derive(Component, PartialEq, Eq)]
struct Earth;
#[derive(Component, PartialEq, Reflect)]
struct Spin(f32);
fn spin(time: Res<Time>, mut query: Query<(&mut Transform, &Spin)>) {
for (mut transform, spin) in query.iter_mut() {
transform.rotate_y(spin.0 * time.delta_seconds());
}
}
#[derive(Resource)]
struct Normal(Option<Handle<Image>>);
fn update_normal(
mut already_ran: Local<bool>,
mut images: ResMut<Assets<Image>>,
normal: Res<Normal>,
) {
if *already_ran {
return;
}
if let Some(normal) = normal.0.as_ref() {
if let Some(mut image) = images.get_mut(normal) {
image.texture_descriptor.format = TextureFormat::Rgba8Unorm;
*already_ran = true;
}
}
}
fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<ParallaxMaterial>>,
mut normal: ResMut<Normal>,
assets: Res<AssetServer>,
) {
use bevy::math::EulerRot::XYZ;
let normal_handle = assets.load(NORMAL_MAP);
normal.0 = Some(normal_handle.clone());
let mut sphere: Mesh = shape::UVSphere::default().into();
sphere.generate_tangents().unwrap();
commands
.spawn(MaterialMeshBundle {
transform: Transform::from_rotation(Quat::from_euler(XYZ, -TAU / 4.0, 0.0, TAU / 2.0)),
mesh: meshes.add(sphere),
material: materials.add(ParallaxMaterial {
perceptual_roughness: 0.75,
base_color_texture: Some(assets.load(ALBEDO_MAP)),
emissive: Color::rgb_u8(30, 30, 30),
emissive_texture: Some(assets.load(EMI_MAP)),
normal_map_texture: normal_handle,
height_map: assets.load(HEIGHT_MAP),
metallic_roughness_texture: Some(assets.load(ROUGH_MAP)),
height_depth: 0.01,
algorithm: ParallaxAlgo::ReliefMapping,
max_height_layers: 128.0,
flip_normal_map_y: false,
..default()
}),
..default()
})
.insert((Earth, Spin(0.1), Name::new("Earth")));
commands
.spawn(PointLightBundle {
point_light: PointLight {
intensity: 500.0,
..default()
},
transform: Transform::from_xyz(2.0, 0.5, 2.0),
..default()
})
.with_children(|cmd| {
let sphere = shape::Icosphere {
radius: 0.05,
subdivisions: 3,
};
cmd.spawn(PbrBundle {
mesh: meshes.add(sphere.try_into().unwrap()),
..default()
});
});
let mut camera = commands.spawn((
Camera3dBundle {
camera: Camera {
hdr: !cfg!(target_arch = "wasm32"),
..default()
},
transform: Transform::from_xyz(3.9, 0.0, 0.0).looking_at(Vec3::ZERO, Vec3::Y),
..default()
},
PanOrbitCamera::default(),
));
#[cfg(not(target_arch = "wasm32"))]
camera.insert(BloomSettings {
intensity: 0.1,
..default()
});
}
#[derive(Component)]
struct PanOrbitCamera {
pub focus: Vec3,
pub radius: f32,
pub upside_down: bool,
}
impl Default for PanOrbitCamera {
fn default() -> Self {
PanOrbitCamera {
focus: Vec3::ZERO,
radius: 5.0,
upside_down: false,
}
}
}
fn pan_orbit_camera(
mut ev_motion: EventReader<MouseMotion>,
mut ev_scroll: EventReader<MouseWheel>,
mouse: Res<Input<MouseButton>>,
keyboard: Res<Input<KeyCode>>,
mut query: Query<(&mut PanOrbitCamera, &mut Transform, &Projection)>,
) {
let right = MouseButton::Right;
let left = MouseButton::Left;
let middle = MouseButton::Middle;
let rotation_speed = 0.001;
let mut pan = Vec2::ZERO;
let mut rotation_move = Vec2::ZERO;
let wasm = cfg!(target_arch = "wasm32");
let scroll = if wasm { 0.01 } else { 1.0 };
let scroll = ev_scroll.iter().map(|e| e.y * scroll).sum::<f32>();
let shift_held = keyboard.pressed(KeyCode::LShift) || keyboard.pressed(KeyCode::RShift);
if (mouse.pressed(right) && !wasm) || (shift_held && mouse.pressed(left)) {
rotation_move = ev_motion.iter().map(|ev| &ev.delta).sum();
} else if mouse.pressed(middle) {
pan = ev_motion.iter().map(|ev| &ev.delta).sum();
}
let right_press = !wasm && (mouse.just_released(right) || mouse.just_pressed(right));
let left_press = shift_held && (mouse.just_released(left) || mouse.just_pressed(left));
let orbit_button_changed = right_press || left_press;
for (mut pan_orbit, mut transform, projection) in query.iter_mut() {
if orbit_button_changed {
let up = transform.rotation * Vec3::Y;
pan_orbit.upside_down = up.y <= 0.0;
}
let mut any = false;
if rotation_move.length_squared() > 0.0 {
any = true;
let sign = if pan_orbit.upside_down { -1.0 } else { 1.0 };
let delta_x = sign * rotation_move.x * TAU * rotation_speed;
let delta_y = rotation_move.y * TAU * rotation_speed / 2.0;
let yaw = Quat::from_rotation_y(-delta_x);
let pitch = Quat::from_rotation_x(-delta_y);
transform.rotation = yaw * transform.rotation * pitch;
} else if pan.length_squared() > 0.0 {
any = true;
if let Projection::Perspective(projection) = projection {
pan *= Vec2::new(projection.fov * projection.aspect_ratio, projection.fov)
* rotation_speed;
}
let right = transform.rotation * Vec3::X * -pan.x;
let up = transform.rotation * Vec3::Y * pan.y;
let translation = (right + up) * pan_orbit.radius;
pan_orbit.focus += translation;
} else if scroll.abs() > 0.0 {
any = true;
pan_orbit.radius -= scroll * pan_orbit.radius * 0.2;
pan_orbit.radius = pan_orbit.radius.max(1.1).min(30.0);
}
if any {
let rot_matrix = Mat3::from_quat(transform.rotation);
transform.translation =
pan_orbit.focus + rot_matrix.mul_vec3(Vec3::new(0.0, 0.0, pan_orbit.radius));
}
}
}