use bevy::{ prelude::{shape::Icosphere, *}, render::mesh::Indices, }; use hexasphere::shapes::IcoSphere; use noise::{HybridMulti, NoiseFn, Perlin}; use wgpu::PrimitiveTopology; use crate::Label; pub const PLANET_RADIUS: f32 = 5000.0; pub(crate) const SPAWN_ALTITUDE: f32 = PLANET_RADIUS * 1.095; #[derive(Component, Debug)] pub struct CyberBike; #[derive(Component)] pub struct CyberSphere; fn spawn_giant_sphere( mut commands: Commands, mut meshes: ResMut>, mut materials: ResMut>, ) { let color = Color::DARK_GRAY; let isphere = shape::Icosphere { radius: PLANET_RADIUS, subdivisions: 40, }; let pmesh = gen_planet(isphere); commands .spawn_bundle(PbrBundle { mesh: meshes.add(pmesh), material: materials.add(StandardMaterial { base_color: color, metallic: 0.1, perceptual_roughness: 0.3, alpha_mode: AlphaMode::Opaque, ..Default::default() }), ..Default::default() }) .insert(CyberSphere); } fn spawn_cyberbike(mut commands: Commands, asset_server: Res) { commands .spawn_bundle(( Transform { translation: Vec3::new(SPAWN_ALTITUDE, 0.0, 0.0), ..Default::default() } .looking_at(Vec3::new(PLANET_RADIUS, 1000.0, 0.0), Vec3::X), GlobalTransform::identity(), )) .with_children(|rider| { rider.spawn_scene(asset_server.load("cyber-bike_no_y_up.glb#Scene0")); }) .insert(CyberBike); } pub struct CyberGeomPlugin; impl Plugin for CyberGeomPlugin { fn build(&self, app: &mut App) { app.add_startup_system(spawn_giant_sphere.label(Label::Geometry)) .add_startup_system(spawn_cyberbike.label(Label::Geometry)); } } //--------------------------------------------------------------------- // utils //--------------------------------------------------------------------- fn gen_planet(sphere: Icosphere) -> Mesh { // straight-up stolen from Bevy's impl of Mesh from Icosphere, so I can do the // displacement before normals are calculated. let generated = IcoSphere::new(sphere.subdivisions, |point| { let inclination = point.y.acos(); let azimuth = point.z.atan2(point.x); let norm_inclination = inclination / std::f32::consts::PI; let norm_azimuth = 0.5 - (azimuth / std::f32::consts::TAU); [norm_azimuth, norm_inclination] }); // TODO: use displaced points for normals by replacing raw_points with // noise-displaced points. let noise = HybridMulti::::default(); let raw_points = generated .raw_points() .iter() .map(|&p| { let disp = noise.get(p.as_dvec3().into()) as f32 * 0.05; let pt = p + disp; pt.into() }) .collect::>(); let points = raw_points .iter() .map(|&p| (Vec3::from_slice(&p) * sphere.radius).into()) .collect::>(); let normals = raw_points .iter() .copied() .map(Into::into) .collect::>(); let uvs = generated.raw_data().to_owned(); let mut indices = Vec::with_capacity(generated.indices_per_main_triangle() * 20); for i in 0..20 { generated.get_indices(i, &mut indices); } let indices = Indices::U32(indices); let mut mesh = Mesh::new(PrimitiveTopology::TriangleList); mesh.set_indices(Some(indices)); mesh.set_attribute(Mesh::ATTRIBUTE_POSITION, points); mesh.set_attribute(Mesh::ATTRIBUTE_NORMAL, normals); mesh.set_attribute(Mesh::ATTRIBUTE_UV_0, uvs); mesh }