146 lines
4 KiB
Rust
146 lines
4 KiB
Rust
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<Assets<Mesh>>,
|
|
mut materials: ResMut<Assets<StandardMaterial>>,
|
|
) {
|
|
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::<SuperSimplex>::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::<Vec<[f32; 3]>>();
|
|
|
|
let points = noisy_points
|
|
.iter()
|
|
.map(|&p| (Vec3::from_slice(&p) * radius).into())
|
|
.collect::<Vec<[f32; 3]>>();
|
|
|
|
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)
|
|
}
|