use avian3d::prelude::*; use bevy::{ prelude::*, render::{ mesh::{Indices, PrimitiveTopology}, render_asset::RenderAssetUsages, }, }; use hexasphere::shapes::IcoSphere; use noise::{HybridMulti, NoiseFn, SuperSimplex}; use rand::{Rng, SeedableRng}; pub const PLANET_RADIUS: f32 = 4_000.0; pub const PLANET_HUE: f32 = 31.0; pub const PLANET_SATURATION: f32 = 1.0; #[derive(Component)] pub struct CyberPlanet; fn spawn_planet( mut commands: Commands, mut meshes: ResMut>, mut materials: ResMut>, ) { let (mesh, shape) = gen_planet(PLANET_RADIUS); let pcollide = ( shape, Friction { static_coefficient: 0.8, dynamic_coefficient: 0.5, combine_rule: CoefficientCombine::Average, }, Restitution::new(0.8), ); commands .spawn(( Mesh3d(meshes.add(mesh)), MeshMaterial3d(materials.add(Color::WHITE)), Transform::default(), Visibility::Visible, )) .insert(( RigidBody::Static, pcollide, CyberPlanet, CollisionLayers::new(LayerMask::ALL, LayerMask::ALL), CollisionMargin(0.2), )); } pub struct CyberPlanetPlugin; impl Plugin for CyberPlanetPlugin { fn build(&self, app: &mut App) { app.add_systems(Startup, spawn_planet); } } //--------------------------------------------------------------------- // utils //--------------------------------------------------------------------- fn gen_planet(radius: f32) -> (Mesh, Collider) { // 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(79, |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] }); let noise = HybridMulti::::default(); let (mut min, mut max) = (f32::MAX, f32::MIN); let noisy_points = generated .raw_points() .iter() .map(|&p| { let disp = (noise.get(p.as_dvec3().into()) * 0.03f64) as f32; let pt = p + (p.normalize() * disp); pt.into() }) .collect::>(); let points = noisy_points .iter() .map(|&p| (Vec3::from_slice(&p) * radius).into()) .collect::>(); for p in &points { let v = Vec3::new(p[0], p[1], p[2]); let v = v.length(); min = v.min(min); max = v.max(max); } let indices = generated.get_all_indices(); let mut idxs = Vec::new(); for idx in indices.chunks_exact(3) { idxs.push([idx[0], idx[1], idx[2]]); } let indices = Indices::U32(indices); let collider = Collider::trimesh(points.iter().map(|p| Vec3::from_slice(p)).collect(), idxs); let mut mesh = Mesh::new( PrimitiveTopology::TriangleList, RenderAssetUsages::default(), ); mesh.insert_indices(indices); mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, points); //mesh.insert_attribute(Mesh::ATTRIBUTE_UV_0, uvs); mesh.duplicate_vertices(); mesh.compute_flat_normals(); let tri_list = mesh .attribute(Mesh::ATTRIBUTE_POSITION) .unwrap() .as_float3() .unwrap(); let mut rng = rand::rngs::StdRng::seed_from_u64(57); let mut colors = Vec::new(); for _triangle in tri_list.chunks_exact(3) { let l = 0.41; let jitter = rng.gen_range(-0.0..=360.0f32); let h = jitter; let color = Color::hsl(h, PLANET_SATURATION, l) .to_linear() .to_f32_array(); for _ in 0..3 { colors.push(color); } } mesh.insert_attribute(Mesh::ATTRIBUTE_COLOR, colors); (mesh, collider) }