here is the code, should be as simple as just making a rust project and add bevy then swap main.rs with this code. might be a bit heavy so so feel free to reduce (GRID_X, GRID_Z, GRID_Y and the time resource which is 24.0)
code
use std::f32::consts::PI;
use bevy::prelude::*;
fn main() {
App::new()
.add_plugins((
DefaultPlugins,
bevy::pbr::wireframe::WireframePlugin::default(),
))
.insert_resource(Time::<Fixed>::from_hz(24.0)) // has to update every now and then to change the transformation values, lower values are more efficient but makes it choppy
.add_systems(Startup, setup)
.add_systems(Update, (mouse_rotation, player_movement, follow_player).chain())
.add_systems(FixedUpdate, update_boxes)
.add_systems(Update, show_position)
.run();
}
#[derive(Component)]
struct Position(Vec3);
#[derive(Component)]
struct Player;
#[derive(Component)]
struct PlanetCoords {
x: f32,
y: f32,
z: f32,
}
#[derive(Component)]
struct PlanetRotation {
yaw: f32,
pitch: f32,
}
const GRID_X: usize = 100;
const GRID_Z: usize = 40;
const GRID_Y: usize = 1;
const TAU: f32 = std::f32::consts::TAU;
struct TorusTransform {
translation: Vec3,
rotation: Quat,
scale: Vec3,
}
fn planet_coords_to_transform(position: Vec3, player_pos: Vec3) -> TorusTransform {
//to avoid "as f32" spam
const FGRID_X: f32 = GRID_X as f32;
const FGRID_Y: f32 = GRID_Y as f32;
const FGRID_Z: f32 = GRID_Z as f32;
//planetary constants that can be moved out to into something like a planet::new()
//they arent dependent on cube position
let correction = FGRID_Z * (1.0 - FGRID_X / (FGRID_X * FGRID_X + FGRID_Z * FGRID_Z).sqrt());
let r0 = FGRID_X / TAU;
let r1 = (FGRID_Z - correction) / TAU;
let du = 2.0 * PI / FGRID_X;
let dv = TAU / FGRID_Z;
let r = r1 / r0;
let sqrt_factor = ((1.0 + r) / (1.0 - r)).sqrt();
//dynamic part
//to be fair, half of the math here is just. "uhm, this artifact is weird, where can i add in some numbers to make it go away?"
let u = position.x / FGRID_X * TAU;
let arg = position.z / FGRID_Z * PI;
let layer = position.y - player_pos.y.clamp(0.0, FGRID_Y) + 1.0;
let tan_half_b = sqrt_factor * f32::tan(arg);
let b = 2.0 * f32::atan(tan_half_b);
let cos_u = u.cos();
let sin_u = u.sin();
let cos_b = b.cos();
let sin_b = b.sin();
let rho = r0 + r1 * cos_b;
let x = rho * cos_u;
let y = rho * sin_u;
let z = r1 * sin_b;
let tangent_u = Vec3::new(-sin_u, cos_u, 0.0);
let tangent_b = Vec3::new(-sin_b * cos_u, -sin_b * sin_u, cos_b);
let normal = Vec3::new(cos_b * cos_u, cos_b * sin_u, sin_b);
let rotation = Quat::from_mat3(&Mat3::from_cols(tangent_u, normal, -tangent_b));
let arg_player = player_pos.z / FGRID_Z * PI;
let tan_half_b_player = sqrt_factor * f32::tan(arg_player);
let b_player = 2.0 * f32::atan(tan_half_b_player);
let width_correction = (1.0 + r * b_player.cos()) * (FGRID_Z / FGRID_X).hypot(1.0);
let exp_factor = (layer * dv * width_correction).exp();
let offset = normal * (-r1 * (1.0 - exp_factor));
let translation = Vec3::new(x, y, z) + offset;
let scale = Vec3::new((r0 + r1 * cos_b * exp_factor) * du,exp_factor * width_correction * dv * r1, rho * du * exp_factor);
TorusTransform { translation, rotation, scale }
}
fn update_boxes(
mut query: Query<(&mut Transform, &Position), With<Mesh3d>>,
player_query: Query<&PlanetCoords, With<Player>>
) {
let player = player_query.single().unwrap();
query.par_iter_mut().for_each(|(mut transform, position)| {
let new_transform = planet_coords_to_transform(position.0, Vec3::new(player.x, player.y, player.z));
transform.translation = new_transform.translation;
transform.rotation = new_transform.rotation;
transform.scale = new_transform.scale;
});
}
const PLAYER_SPEED: f32 = 2.0;
const MOUSE_SENSITIVITY: f32 = 0.005;
fn mouse_rotation(
mouse_motion: Res<bevy::input::mouse::AccumulatedMouseMotion>,
mut player_query: Query<&mut PlanetRotation, With<Player>>,
) {
let delta = mouse_motion.delta;
if delta != Vec2::ZERO && let Ok(mut coords) = player_query.single_mut() {
coords.yaw += -delta.x * MOUSE_SENSITIVITY;
coords.pitch += -delta.y * MOUSE_SENSITIVITY;
coords.pitch = coords.pitch.clamp(-1.4, 1.4);
}
}
fn player_movement(
mut player_query: Query<(&mut Transform, &mut PlanetCoords, &PlanetRotation), With<Player>>,
input: Res<ButtonInput<KeyCode>>,
time: Res<Time>,
) {
let (mut transform, mut coords, rot) = player_query.single_mut().unwrap();
let base_rot = planet_coords_to_transform(Vec3::new(coords.x, coords.y, coords.z), Vec3::new(coords.x, coords.y, coords.z));
let basis = Mat3::from_quat(base_rot.rotation);
let tangent_u = basis.col(0);
let tangent_b = basis.col(1);
let yaw = rot.yaw;
let forward = -yaw.sin() * tangent_u + yaw.cos() * tangent_b;
let right = yaw.cos() * tangent_u + yaw.sin() * tangent_b;
let mut speed = PLAYER_SPEED;
if input.pressed(KeyCode::ControlLeft) {
speed *= 100.0;
}
let mut move_dir = Vec3::ZERO;
if input.pressed(KeyCode::KeyW) {
move_dir += speed * forward;
}
if input.pressed(KeyCode::KeyS) {
move_dir -= speed * forward;
}
if input.pressed(KeyCode::KeyA) {
move_dir -= speed * right;
}
if input.pressed(KeyCode::KeyD) {
move_dir += speed * right;
}
if input.pressed(KeyCode::Space) {
coords.y += speed * time.delta_secs();
}
if input.pressed(KeyCode::ShiftLeft) {
coords.y -= speed * time.delta_secs();
}
if move_dir != Vec3::ZERO {
move_dir = move_dir.normalize();
let du = move_dir.dot(tangent_u) * speed * time.delta_secs();
let dv = move_dir.dot(tangent_b) * speed * time.delta_secs();
coords.x += du;
coords.z += dv;
}
let base = planet_coords_to_transform(Vec3::new(coords.x, coords.y, coords.z), Vec3::new(coords.x, coords.y, coords.z));
let final_rot = base_rot.rotation * Quat::from_axis_angle(Vec3::Y, rot.yaw);
transform.translation = base.translation;
transform.rotation = final_rot;
}
fn follow_player(
player_query: Query<(&Transform, &PlanetRotation), (With<Player>, Without<Camera3d>)>,
mut camera_query: Query<&mut Transform, (With<Camera3d>, Without<Player>)>,
) {
let (player_transform, rot) = player_query.single().unwrap();
let mut cam_transform = camera_query.single_mut().unwrap();
let eye_pos = player_transform.translation;
let pitch_rot = Quat::from_axis_angle(*player_transform.local_x(), rot.pitch);
let final_rot = pitch_rot * player_transform.rotation;
cam_transform.translation = eye_pos;
cam_transform.rotation = final_rot;
}
fn show_position(
camera_query: Query<&Transform, (Without<Mesh3d>, With<Camera3d>)>,
player_query: Query<&PlanetCoords, With<Player>>,
mut windows: Query<&mut Window, With<bevy::window::PrimaryWindow>>,
time: Res<Time>,
) {
let mut camera_position = Vec3::ZERO;
let mut local_position = Vec3::ZERO;
for camera_transform in camera_query.iter() {
camera_position = camera_transform.translation;
}
for localtransform in player_query.iter() {
local_position = Vec3::new(localtransform.x, localtransform.y, localtransform.z);
}
let fps = 1.0 / time.delta_secs();
for mut window in windows.iter_mut() {
window.title = format!("{}, {} | FPS: {:.0}", camera_position, local_position, fps);
}
}
fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
let cube_mesh = meshes.add(Cuboid::new(1.0, 1.0, 1.0));
for j in 0..GRID_Z {
for k in 0..GRID_Y {
for i in 0..GRID_X {
//if i < 50 {continue;}// do this to see the inside
//if j.rem_euclid(2) == 1 {continue;}
let hue_theta = i as f32 / GRID_X as f32;
let hue_phi = j as f32 / GRID_Z as f32;
let depth_t = k as f32 / GRID_Y as f32;
let cr = 0.2 + 0.7 * hue_theta;
let cg = 0.1 + 0.5 * depth_t;
let cb = 0.4 + 0.6 * hue_phi;
commands.spawn((
Mesh3d(cube_mesh.clone()),
MeshMaterial3d(materials.add(StandardMaterial {
base_color: Color::srgb(cr, cg, cb),
..default()
})),
Position(Vec3 {
x: i as f32,
y: k as f32,
z: j as f32,
}),
Transform::from_xyz(0.0, 0.0, 0.0),
));
}}}
commands.spawn((
PointLight {
intensity: 5_000_000.0,
shadows_enabled: true,
..default()
},
Transform::from_xyz(6.0, 8.0, 6.0),
));
commands.spawn((
PointLight {
intensity: 2_000_000.0,
..default()
},
Transform::from_xyz(-6.0, -4.0, -6.0),
));
commands.spawn((
Camera3d::default(),
Transform::from_xyz(0.0, 3.0, 8.0).looking_at(Vec3::ZERO, Vec3::Y),
));
commands.spawn((
Player,
PlanetCoords {
x: 0.0,
y: GRID_Y as f32,
z: 0.0,
},
PlanetRotation{
yaw: 0.0,
pitch: 0.0,
},
Mesh3d(meshes.add(Sphere::new(0.3))),
MeshMaterial3d(materials.add(StandardMaterial {
base_color: Color::srgb(1.0, 0.8, 0.2),
..default()
})),
Transform {
..default()
},
));
}