initial working tree
This commit is contained in:
commit
33901a170d
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/target
|
||||||
5164
Cargo.lock
generated
Normal file
5164
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
15
Cargo.toml
Normal file
15
Cargo.toml
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
[package]
|
||||||
|
name = "tree-generation"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
bevy = "0.16"
|
||||||
|
iyes_perf_ui = "0.5.0"
|
||||||
|
|
||||||
|
|
||||||
|
[profile.dev]
|
||||||
|
opt-level = 1
|
||||||
|
|
||||||
|
[profile.dev.package."*"]
|
||||||
|
opt-level = 3
|
||||||
104
src/camera.rs
Normal file
104
src/camera.rs
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
use bevy::input::keyboard::KeyCode;
|
||||||
|
use bevy::input::mouse::MouseMotion;
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy::window::CursorGrabMode;
|
||||||
|
pub struct CameraPlugin;
|
||||||
|
|
||||||
|
impl Plugin for CameraPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
app.add_systems(Startup, camera_setup);
|
||||||
|
app.add_systems(Update, camera_movement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
struct MainCamera {
|
||||||
|
pitch: f32, // up/down rotation in radians
|
||||||
|
yaw: f32, // left/right rotation in radians
|
||||||
|
}
|
||||||
|
|
||||||
|
fn camera_setup(mut commands: Commands) {
|
||||||
|
// camera
|
||||||
|
commands.spawn((
|
||||||
|
Camera3d::default(),
|
||||||
|
Transform::from_xyz(0.0, 2.0, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
|
||||||
|
GlobalTransform::default(),
|
||||||
|
MainCamera {
|
||||||
|
pitch: -0.15 * std::f32::consts::FRAC_PI_2,
|
||||||
|
yaw: 0.0,
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn camera_movement(
|
||||||
|
time: Res<Time>,
|
||||||
|
keyboard: Res<ButtonInput<KeyCode>>,
|
||||||
|
mouse_buttons: Res<ButtonInput<MouseButton>>,
|
||||||
|
mut mouse_motion_events: EventReader<MouseMotion>,
|
||||||
|
mut query: Query<(&mut Transform, &mut MainCamera)>,
|
||||||
|
mut window: Single<&mut Window>,
|
||||||
|
) {
|
||||||
|
let (mut transform, mut cam) = query.single_mut().unwrap();
|
||||||
|
|
||||||
|
// Mouse sensitivity & clamp
|
||||||
|
let mouse_sensitivity = 0.005;
|
||||||
|
let max_pitch = std::f32::consts::FRAC_PI_2 * 0.99; // ~89 degrees
|
||||||
|
|
||||||
|
if mouse_buttons.just_pressed(MouseButton::Left) {
|
||||||
|
window.cursor_options.visible = false;
|
||||||
|
window.cursor_options.grab_mode = CursorGrabMode::Locked;
|
||||||
|
}
|
||||||
|
|
||||||
|
if mouse_buttons.just_released(MouseButton::Left) {
|
||||||
|
window.cursor_options.visible = true;
|
||||||
|
window.cursor_options.grab_mode = CursorGrabMode::None;
|
||||||
|
}
|
||||||
|
|
||||||
|
if mouse_buttons.pressed(MouseButton::Left) {
|
||||||
|
// Process mouse movement to update pitch and yaw
|
||||||
|
for event in mouse_motion_events.read() {
|
||||||
|
cam.yaw -= event.delta.x * mouse_sensitivity;
|
||||||
|
cam.pitch -= event.delta.y * mouse_sensitivity;
|
||||||
|
|
||||||
|
// Clamp pitch to avoid flipping
|
||||||
|
cam.pitch = cam.pitch.clamp(-max_pitch, max_pitch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build rotation quaternion from yaw (around Y) and pitch (around X)
|
||||||
|
let yaw_rot = Quat::from_rotation_y(cam.yaw);
|
||||||
|
let pitch_rot = Quat::from_rotation_x(cam.pitch);
|
||||||
|
transform.rotation = yaw_rot * pitch_rot;
|
||||||
|
|
||||||
|
// Movement vector
|
||||||
|
let mut direction = Vec3::ZERO;
|
||||||
|
let forward: Vec3 = transform.forward().into();
|
||||||
|
let right: Vec3 = transform.right().into();
|
||||||
|
let up: Vec3 = transform.up().into();
|
||||||
|
|
||||||
|
let speed = 10.0;
|
||||||
|
let delta = time.delta().as_secs_f32();
|
||||||
|
|
||||||
|
if keyboard.pressed(KeyCode::KeyW) {
|
||||||
|
direction += forward;
|
||||||
|
}
|
||||||
|
if keyboard.pressed(KeyCode::KeyS) {
|
||||||
|
direction -= forward;
|
||||||
|
}
|
||||||
|
if keyboard.pressed(KeyCode::KeyA) {
|
||||||
|
direction -= right;
|
||||||
|
}
|
||||||
|
if keyboard.pressed(KeyCode::KeyD) {
|
||||||
|
direction += right;
|
||||||
|
}
|
||||||
|
if keyboard.pressed(KeyCode::Space) {
|
||||||
|
direction += up;
|
||||||
|
}
|
||||||
|
if keyboard.pressed(KeyCode::ShiftLeft) {
|
||||||
|
direction -= up;
|
||||||
|
}
|
||||||
|
|
||||||
|
if direction.length() > 0.0 {
|
||||||
|
transform.translation += direction.normalize() * speed * delta;
|
||||||
|
}
|
||||||
|
}
|
||||||
34
src/debugging.rs
Normal file
34
src/debugging.rs
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
use bevy::diagnostic::{
|
||||||
|
EntityCountDiagnosticsPlugin, FrameTimeDiagnosticsPlugin, SystemInformationDiagnosticsPlugin,
|
||||||
|
};
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy::render::diagnostic::RenderDiagnosticsPlugin;
|
||||||
|
use iyes_perf_ui::prelude::*;
|
||||||
|
|
||||||
|
pub struct DebuggingPlugin;
|
||||||
|
|
||||||
|
impl Plugin for DebuggingPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
app.add_systems(Startup, debugging_setup)
|
||||||
|
.add_plugins(FrameTimeDiagnosticsPlugin::default())
|
||||||
|
.add_plugins(EntityCountDiagnosticsPlugin::default())
|
||||||
|
.add_plugins(SystemInformationDiagnosticsPlugin::default())
|
||||||
|
.add_plugins(RenderDiagnosticsPlugin::default())
|
||||||
|
.add_plugins(PerfUiPlugin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn debugging_setup(mut commands: Commands) {
|
||||||
|
commands.spawn((
|
||||||
|
PerfUiEntryFPSAverage::default(),
|
||||||
|
PerfUiEntryFrameTime::default(),
|
||||||
|
PerfUiEntryEntityCount::default(),
|
||||||
|
PerfUiEntryCpuUsage::default(),
|
||||||
|
PerfUiEntrySystemCpuUsage::default(),
|
||||||
|
PerfUiEntryMemUsage::default(),
|
||||||
|
PerfUiEntryRenderCpuTime::default(),
|
||||||
|
PerfUiEntryWindowResolution::default(),
|
||||||
|
PerfUiEntryWindowMode::default(),
|
||||||
|
PerfUiEntryWindowPresentMode::default(),
|
||||||
|
));
|
||||||
|
}
|
||||||
427
src/main.rs
Normal file
427
src/main.rs
Normal file
@ -0,0 +1,427 @@
|
|||||||
|
use std::f32::consts::PI;
|
||||||
|
|
||||||
|
use bevy::asset::RenderAssetUsages;
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy::render::mesh::{PrimitiveTopology, VertexAttributeValues};
|
||||||
|
use bevy::window::PresentMode;
|
||||||
|
use tree::{ConeSegment, TreeNode};
|
||||||
|
|
||||||
|
mod camera;
|
||||||
|
mod debugging;
|
||||||
|
mod tree;
|
||||||
|
mod tree_rendering;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
.add_plugins(DefaultPlugins)
|
||||||
|
.add_plugins(debugging::DebuggingPlugin)
|
||||||
|
.add_plugins(camera::CameraPlugin)
|
||||||
|
.add_systems(Startup, setup)
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup(
|
||||||
|
mut commands: Commands,
|
||||||
|
mut meshes: ResMut<Assets<Mesh>>,
|
||||||
|
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||||
|
mut window: Single<&mut Window>,
|
||||||
|
) {
|
||||||
|
// window.present_mode = PresentMode::AutoNoVsync;
|
||||||
|
|
||||||
|
let a = Mesh3d(meshes.add(Circle::new(4.0)));
|
||||||
|
|
||||||
|
// circular base
|
||||||
|
commands.spawn((
|
||||||
|
Mesh3d(meshes.add(Circle::new(4.0))),
|
||||||
|
MeshMaterial3d(materials.add(Color::WHITE)),
|
||||||
|
Transform::from_rotation(Quat::from_rotation_x(-std::f32::consts::FRAC_PI_2)),
|
||||||
|
));
|
||||||
|
// cube
|
||||||
|
// commands.spawn((
|
||||||
|
// Mesh3d(meshes.add(Cuboid::new(1.0, 1.0, 1.0))),
|
||||||
|
// MeshMaterial3d(materials.add(Color::srgb_u8(124, 144, 255))),
|
||||||
|
// Transform::from_xyz(0.0, 0.5, 0.0),
|
||||||
|
// ));
|
||||||
|
// light
|
||||||
|
// commands.spawn((
|
||||||
|
// PointLight {
|
||||||
|
// shadows_enabled: true,
|
||||||
|
// intensity: 10_000_000.,
|
||||||
|
// range: 40.,
|
||||||
|
// ..default()
|
||||||
|
// },
|
||||||
|
// Transform::from_xyz(8.0, 16.0, 8.0),
|
||||||
|
// ));
|
||||||
|
|
||||||
|
commands.spawn((
|
||||||
|
DirectionalLight {
|
||||||
|
shadows_enabled: true,
|
||||||
|
illuminance: 3_000.,
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
Transform {
|
||||||
|
translation: Vec3::new(0.0, 2.0, 0.0),
|
||||||
|
rotation: Quat::from_rotation_x(-PI / 4.) * Quat::from_rotation_y(-PI / 4.),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
// Transform::from_xyz(0.0, 16.0, 0.0),
|
||||||
|
));
|
||||||
|
|
||||||
|
// let cone_mesh = tree::generate_truncated_cone(1., 2., 2., 200);
|
||||||
|
|
||||||
|
let mut cone_mesh = Mesh::new(
|
||||||
|
PrimitiveTopology::TriangleList,
|
||||||
|
RenderAssetUsages::MAIN_WORLD | RenderAssetUsages::RENDER_WORLD,
|
||||||
|
);
|
||||||
|
// tree::append_connected_truncated_cones_to_mesh(
|
||||||
|
// &mut cone_mesh,
|
||||||
|
// &[
|
||||||
|
// tree::ConeSegment {
|
||||||
|
// radius: 2.,
|
||||||
|
// offset: 0.,
|
||||||
|
// },
|
||||||
|
// tree::ConeSegment {
|
||||||
|
// radius: 1.,
|
||||||
|
// offset: 2.,
|
||||||
|
// },
|
||||||
|
// tree::ConeSegment {
|
||||||
|
// radius: 1.,
|
||||||
|
// offset: 1.,
|
||||||
|
// },
|
||||||
|
// tree::ConeSegment {
|
||||||
|
// radius: 2.,
|
||||||
|
// offset: 2.,
|
||||||
|
// },
|
||||||
|
// tree::ConeSegment {
|
||||||
|
// radius: 2.,
|
||||||
|
// offset: 0.5,
|
||||||
|
// },
|
||||||
|
// tree::ConeSegment {
|
||||||
|
// radius: 1.,
|
||||||
|
// offset: 2.,
|
||||||
|
// },
|
||||||
|
// tree::ConeSegment {
|
||||||
|
// radius: 0.,
|
||||||
|
// offset: 1.,
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// 20,
|
||||||
|
// );
|
||||||
|
// tree::append_connected_truncated_cones_to_mesh(
|
||||||
|
// &mut cone_mesh,
|
||||||
|
// &[
|
||||||
|
// tree::ConeSegment {
|
||||||
|
// radius: 0.5,
|
||||||
|
// offset: 0.,
|
||||||
|
// angle: 0.,
|
||||||
|
// rotation: 0.,
|
||||||
|
// },
|
||||||
|
// tree::ConeSegment {
|
||||||
|
// radius: 0.5,
|
||||||
|
// offset: 2.,
|
||||||
|
// angle: 0.,
|
||||||
|
// rotation: 0.,
|
||||||
|
// },
|
||||||
|
// tree::ConeSegment {
|
||||||
|
// radius: 0.5,
|
||||||
|
// offset: 2.,
|
||||||
|
// angle: 0.2,
|
||||||
|
// rotation: PI/2.,
|
||||||
|
// },
|
||||||
|
// tree::ConeSegment {
|
||||||
|
// radius: 0.5,
|
||||||
|
// offset: 2.,
|
||||||
|
// angle: 0.2,
|
||||||
|
// rotation: PI/2.,
|
||||||
|
// },
|
||||||
|
// tree::ConeSegment {
|
||||||
|
// radius: 0.5,
|
||||||
|
// offset: 2.,
|
||||||
|
// angle: 0.2,
|
||||||
|
// rotation: PI/2.,
|
||||||
|
// },
|
||||||
|
// tree::ConeSegment {
|
||||||
|
// radius: 0.5,
|
||||||
|
// offset: 2.,
|
||||||
|
// angle: 0.2,
|
||||||
|
// rotation: PI/2.,
|
||||||
|
// },
|
||||||
|
// tree::ConeSegment {
|
||||||
|
// radius: 0.5,
|
||||||
|
// offset: 2.,
|
||||||
|
// angle: 0.2,
|
||||||
|
// rotation: PI/2.,
|
||||||
|
// },
|
||||||
|
// tree::ConeSegment {
|
||||||
|
// radius: 0.4,
|
||||||
|
// offset: 2.,
|
||||||
|
// angle: 0.2,
|
||||||
|
// rotation: PI/2.,
|
||||||
|
// },
|
||||||
|
// tree::ConeSegment {
|
||||||
|
// radius: 0.3,
|
||||||
|
// offset: 2.,
|
||||||
|
// angle: 0.2,
|
||||||
|
// rotation: PI/2.,
|
||||||
|
// },
|
||||||
|
// tree::ConeSegment {
|
||||||
|
// radius: 0.2,
|
||||||
|
// offset: 2.,
|
||||||
|
// angle: 0.2,
|
||||||
|
// rotation: PI/2.,
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// 10,
|
||||||
|
// );
|
||||||
|
|
||||||
|
// commands.spawn((
|
||||||
|
// Mesh3d(meshes.add(cone_mesh)),
|
||||||
|
// MeshMaterial3d(materials.add(Color::srgb_u8(124, 144, 255))),
|
||||||
|
// Transform::from_xyz(0.0, 0.5, 0.0),
|
||||||
|
// ));
|
||||||
|
|
||||||
|
// let tree_mesh = tree_rendering::generate_branch_mesh(&[
|
||||||
|
// &tree::TreeNode {
|
||||||
|
// id: 0,
|
||||||
|
// kind: tree::TreeNodeKind::Extension,
|
||||||
|
// parent_id: 0,
|
||||||
|
// radius: 0.5,
|
||||||
|
// offset: 0.,
|
||||||
|
// angle: 0.,
|
||||||
|
// rotation: 0.,
|
||||||
|
// },
|
||||||
|
// &tree::TreeNode {
|
||||||
|
// id: 0,
|
||||||
|
// kind: tree::TreeNodeKind::Extension,
|
||||||
|
// parent_id: 0,
|
||||||
|
// radius: 0.5,
|
||||||
|
// offset: 2.,
|
||||||
|
// angle: 0.,
|
||||||
|
// rotation: 0.,
|
||||||
|
// },
|
||||||
|
// &tree::TreeNode {
|
||||||
|
// id: 0,
|
||||||
|
// kind: tree::TreeNodeKind::Extension,
|
||||||
|
// parent_id: 0,
|
||||||
|
// radius: 0.5,
|
||||||
|
// offset: 2.,
|
||||||
|
// angle: 0.2,
|
||||||
|
// rotation: PI / 2.,
|
||||||
|
// },
|
||||||
|
// &tree::TreeNode {
|
||||||
|
// id: 0,
|
||||||
|
// kind: tree::TreeNodeKind::Extension,
|
||||||
|
// parent_id: 0,
|
||||||
|
// radius: 0.5,
|
||||||
|
// offset: 2.,
|
||||||
|
// angle: 0.2,
|
||||||
|
// rotation: PI / 2.,
|
||||||
|
// },
|
||||||
|
// &tree::TreeNode {
|
||||||
|
// id: 0,
|
||||||
|
// kind: tree::TreeNodeKind::Extension,
|
||||||
|
// parent_id: 0,
|
||||||
|
// radius: 0.5,
|
||||||
|
// offset: 2.,
|
||||||
|
// angle: 0.2,
|
||||||
|
// rotation: PI / 2.,
|
||||||
|
// },
|
||||||
|
// &tree::TreeNode {
|
||||||
|
// id: 0,
|
||||||
|
// kind: tree::TreeNodeKind::Extension,
|
||||||
|
// parent_id: 0,
|
||||||
|
// radius: 0.5,
|
||||||
|
// offset: 2.,
|
||||||
|
// angle: 0.2,
|
||||||
|
// rotation: PI / 2.,
|
||||||
|
// },
|
||||||
|
// &tree::TreeNode {
|
||||||
|
// id: 0,
|
||||||
|
// kind: tree::TreeNodeKind::Extension,
|
||||||
|
// parent_id: 0,
|
||||||
|
// radius: 0.5,
|
||||||
|
// offset: 2.,
|
||||||
|
// angle: 0.2,
|
||||||
|
// rotation: PI / 2.,
|
||||||
|
// },
|
||||||
|
// &tree::TreeNode {
|
||||||
|
// id: 0,
|
||||||
|
// kind: tree::TreeNodeKind::Extension,
|
||||||
|
// parent_id: 0,
|
||||||
|
// radius: 0.4,
|
||||||
|
// offset: 2.,
|
||||||
|
// angle: 0.2,
|
||||||
|
// rotation: PI / 2.,
|
||||||
|
// },
|
||||||
|
// &tree::TreeNode {
|
||||||
|
// id: 0,
|
||||||
|
// kind: tree::TreeNodeKind::Extension,
|
||||||
|
// parent_id: 0,
|
||||||
|
// radius: 0.3,
|
||||||
|
// offset: 2.,
|
||||||
|
// angle: -0.4,
|
||||||
|
// rotation: PI / 2.,
|
||||||
|
// },
|
||||||
|
// &tree::TreeNode {
|
||||||
|
// id: 0,
|
||||||
|
// kind: tree::TreeNodeKind::EndCut,
|
||||||
|
// parent_id: 0,
|
||||||
|
// radius: 0.2,
|
||||||
|
// offset: 2.,
|
||||||
|
// angle: -0.4,
|
||||||
|
// rotation: PI / 2.,
|
||||||
|
// },
|
||||||
|
// ]);
|
||||||
|
|
||||||
|
// // let normal_material = materials.add(Color::srgb_u8(255, 0, 0));
|
||||||
|
// // draw_normals(&mut meshes, &mut commands, &tree_mesh, &normal_material, &Transform::from_xyz(0.0, 0.5, 0.0));
|
||||||
|
|
||||||
|
// commands.spawn((
|
||||||
|
// Mesh3d(meshes.add(tree_mesh)),
|
||||||
|
// MeshMaterial3d(materials.add(Color::srgb_u8(124, 144, 255))),
|
||||||
|
// Transform::from_xyz(0.0, 0.5, 0.0),
|
||||||
|
// ));
|
||||||
|
|
||||||
|
let tree = initial_tree();
|
||||||
|
let tree_mesh = tree_rendering::generate_tree(&tree);
|
||||||
|
|
||||||
|
commands.spawn((
|
||||||
|
Mesh3d(meshes.add(tree_mesh)),
|
||||||
|
MeshMaterial3d(materials.add(Color::srgb_u8(124, 144, 255))),
|
||||||
|
Transform::from_xyz(0.0, 0.5, 0.0),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_normals(
|
||||||
|
meshes: &mut ResMut<Assets<Mesh>>,
|
||||||
|
commands: &mut Commands,
|
||||||
|
mesh: &Mesh,
|
||||||
|
material: &Handle<StandardMaterial>,
|
||||||
|
transform: &Transform,
|
||||||
|
) {
|
||||||
|
let positions = match mesh.attribute(Mesh::ATTRIBUTE_POSITION).unwrap() {
|
||||||
|
VertexAttributeValues::Float32x3(positions) => positions,
|
||||||
|
_ => return,
|
||||||
|
};
|
||||||
|
let normals = match mesh.attribute(Mesh::ATTRIBUTE_NORMAL).unwrap() {
|
||||||
|
VertexAttributeValues::Float32x3(normals) => normals,
|
||||||
|
_ => return,
|
||||||
|
};
|
||||||
|
|
||||||
|
for (pos, normal) in positions.iter().zip(normals.iter()) {
|
||||||
|
let mut start = Vec3::from(*pos);
|
||||||
|
start.y += 0.5;
|
||||||
|
let end = start + Vec3::from(*normal);
|
||||||
|
|
||||||
|
let mut transform = Transform::from_translation(start).looking_at(end, Vec3::Y);
|
||||||
|
transform.rotate_local_x(PI / 2.);
|
||||||
|
|
||||||
|
commands.spawn((
|
||||||
|
Mesh3d(meshes.add(Cylinder::new(0.01, 0.5))),
|
||||||
|
MeshMaterial3d(material.clone()),
|
||||||
|
transform,
|
||||||
|
// transform.clone().with_translation(start).looking_at(end, Vec3::Y),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fn draw_normals(
|
||||||
|
// meshes: Res<Assets<Mesh>>,
|
||||||
|
// mesh_handle: Res<Handle<Mesh>>,
|
||||||
|
// mut lines: ResMut<DebugLines>,
|
||||||
|
// ) {
|
||||||
|
// if let Some(mesh) = meshes.get(mesh_handle) {
|
||||||
|
// let positions = match mesh.attribute(Mesh::ATTRIBUTE_POSITION).unwrap() {
|
||||||
|
// VertexAttributeValues::Float32x3(positions) => positions,
|
||||||
|
// _ => return,
|
||||||
|
// };
|
||||||
|
// let normals = match mesh.attribute(Mesh::ATTRIBUTE_NORMAL).unwrap() {
|
||||||
|
// VertexAttributeValues::Float32x3(normals) => normals,
|
||||||
|
// _ => return,
|
||||||
|
// };
|
||||||
|
|
||||||
|
// for (pos, normal) in positions.iter().zip(normals.iter()) {
|
||||||
|
// let start = Vec3::from(*pos);
|
||||||
|
// let end = start + Vec3::from(*normal) * 0.2;
|
||||||
|
// lines.line_colored(start, end, 0.0, Color::GREEN);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
fn initial_tree() -> tree::Tree {
|
||||||
|
let mut tree = tree::Tree::default();
|
||||||
|
tree.nodes.insert(
|
||||||
|
0,
|
||||||
|
tree::TreeNode {
|
||||||
|
id: 0,
|
||||||
|
kind: tree::TreeNodeKind::Extension,
|
||||||
|
parent_id: 0,
|
||||||
|
radius: 0.5,
|
||||||
|
offset: 0.,
|
||||||
|
angle: 0.,
|
||||||
|
rotation: 0.,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
tree.nodes.insert(
|
||||||
|
1,
|
||||||
|
tree::TreeNode {
|
||||||
|
id: 1,
|
||||||
|
kind: tree::TreeNodeKind::Extension,
|
||||||
|
parent_id: 0,
|
||||||
|
radius: 0.3,
|
||||||
|
offset: 2.,
|
||||||
|
angle: 0.,
|
||||||
|
rotation: 0.,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
tree.nodes.insert(
|
||||||
|
2,
|
||||||
|
tree::TreeNode {
|
||||||
|
id: 2,
|
||||||
|
kind: tree::TreeNodeKind::EndCut,
|
||||||
|
parent_id: 1,
|
||||||
|
radius: 0.2,
|
||||||
|
offset: 2.,
|
||||||
|
angle: -0.4,
|
||||||
|
rotation: PI / 2.,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
tree.nodes.insert(
|
||||||
|
3,
|
||||||
|
tree::TreeNode {
|
||||||
|
id: 3,
|
||||||
|
kind: tree::TreeNodeKind::StartBranch,
|
||||||
|
parent_id: 1,
|
||||||
|
radius: 0.1,
|
||||||
|
offset: 2.,
|
||||||
|
angle: -0.6,
|
||||||
|
rotation: -PI / 2.,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
tree.nodes.insert(
|
||||||
|
4,
|
||||||
|
tree::TreeNode {
|
||||||
|
id: 4,
|
||||||
|
kind: tree::TreeNodeKind::EndCut,
|
||||||
|
parent_id: 3,
|
||||||
|
radius: 0.05,
|
||||||
|
offset: 2.,
|
||||||
|
angle: -0.4,
|
||||||
|
rotation: PI / 2.,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
tree.nodes.insert(
|
||||||
|
5,
|
||||||
|
tree::TreeNode {
|
||||||
|
id: 5,
|
||||||
|
kind: tree::TreeNodeKind::EndBranch,
|
||||||
|
parent_id: 3,
|
||||||
|
radius: 0.05,
|
||||||
|
offset: 2.,
|
||||||
|
angle: -0.4,
|
||||||
|
rotation: -PI / 2.,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
tree
|
||||||
|
}
|
||||||
337
src/tree.rs
Normal file
337
src/tree.rs
Normal file
@ -0,0 +1,337 @@
|
|||||||
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
f32::consts::{FRAC_PI_2, PI},
|
||||||
|
iter::Map,
|
||||||
|
};
|
||||||
|
|
||||||
|
use bevy::{
|
||||||
|
asset::RenderAssetUsages,
|
||||||
|
math::VectorSpace,
|
||||||
|
prelude::*,
|
||||||
|
render::mesh::{Indices, PrimitiveTopology},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
pub struct Tree {
|
||||||
|
pub age: f32,
|
||||||
|
pub nodes: HashMap<u32, TreeNode>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
pub enum TreeNodeKind {
|
||||||
|
StartBranch,
|
||||||
|
Extension,
|
||||||
|
EndMeristem,
|
||||||
|
EndBranch,
|
||||||
|
EndCut,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct TreeNode {
|
||||||
|
pub id: u32,
|
||||||
|
pub kind: TreeNodeKind,
|
||||||
|
pub parent_id: u32, // if same as ID, then no parent
|
||||||
|
pub radius: f32,
|
||||||
|
pub offset: f32,
|
||||||
|
pub angle: f32,
|
||||||
|
pub rotation: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Tree {
|
||||||
|
fn default() -> Self {
|
||||||
|
Tree {
|
||||||
|
age: 0.,
|
||||||
|
nodes: HashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Tree {
|
||||||
|
pub fn get_accumulated_transform(&self, id: u32) -> Transform {
|
||||||
|
let mut transform = Transform::IDENTITY;
|
||||||
|
let mut curr_node = &self.nodes[&id];
|
||||||
|
loop {
|
||||||
|
transform = curr_node.get_transform() * transform;
|
||||||
|
if curr_node.id == curr_node.parent_id {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
curr_node = &self.nodes[&curr_node.parent_id];
|
||||||
|
}
|
||||||
|
transform
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl TreeNode {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
TreeNode {
|
||||||
|
id: 0,
|
||||||
|
kind: TreeNodeKind::Extension,
|
||||||
|
parent_id: 0,
|
||||||
|
radius: 0.,
|
||||||
|
offset: 0.,
|
||||||
|
angle: 0.,
|
||||||
|
rotation: 0.,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_transform(&self) -> Transform {
|
||||||
|
let mut transform = Transform::IDENTITY;
|
||||||
|
transform.rotate_local_y(self.rotation);
|
||||||
|
transform.rotate_local_x(self.angle);
|
||||||
|
transform.rotate_local_y(-self.rotation);
|
||||||
|
transform.translation += transform.up() * self.offset;
|
||||||
|
transform
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_end(&self) -> bool {
|
||||||
|
matches!(
|
||||||
|
self.kind,
|
||||||
|
TreeNodeKind::EndMeristem | TreeNodeKind::EndCut | TreeNodeKind::EndBranch
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_extension(&self) -> bool {
|
||||||
|
matches!(
|
||||||
|
self.kind,
|
||||||
|
TreeNodeKind::Extension | TreeNodeKind::StartBranch
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_branch_begin(&self) -> bool {
|
||||||
|
self.id == self.parent_id
|
||||||
|
|| matches!(
|
||||||
|
self.kind,
|
||||||
|
TreeNodeKind::StartBranch | TreeNodeKind::EndBranch
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// pub fn generate_truncated_cone(
|
||||||
|
// top_radius: f32,
|
||||||
|
// bottom_radius: f32,
|
||||||
|
// height: f32,
|
||||||
|
// resolution: usize,
|
||||||
|
// ) -> Mesh {
|
||||||
|
// let mut positions = Vec::new();
|
||||||
|
// let mut normals = Vec::new();
|
||||||
|
// let mut uvs = Vec::new();
|
||||||
|
// let mut indices = Vec::new();
|
||||||
|
|
||||||
|
// // === Side Vertices ===
|
||||||
|
// for i in 0..=resolution {
|
||||||
|
// let theta = i as f32 / resolution as f32 * 2.0 * PI;
|
||||||
|
// let (cos, sin) = (theta.cos(), theta.sin());
|
||||||
|
|
||||||
|
// // Bottom ring
|
||||||
|
// let xb = bottom_radius * cos;
|
||||||
|
// let zb = bottom_radius * sin;
|
||||||
|
// positions.push([xb, 0.0, zb]);
|
||||||
|
// normals.push(
|
||||||
|
// Vec3::new(cos, (bottom_radius - top_radius) / height, sin)
|
||||||
|
// .normalize()
|
||||||
|
// .into(),
|
||||||
|
// );
|
||||||
|
// uvs.push([i as f32 / resolution as f32, 0.0]);
|
||||||
|
|
||||||
|
// // Top ring
|
||||||
|
// let xt = top_radius * cos;
|
||||||
|
// let zt = top_radius * sin;
|
||||||
|
// positions.push([xt, height, zt]);
|
||||||
|
// normals.push(
|
||||||
|
// Vec3::new(cos, (bottom_radius - top_radius) / height, sin)
|
||||||
|
// .normalize()
|
||||||
|
// .into(),
|
||||||
|
// );
|
||||||
|
// uvs.push([i as f32 / resolution as f32, 1.0]);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // === Side Indices ===
|
||||||
|
// for i in 0..resolution {
|
||||||
|
// let base = (i * 2) as u32;
|
||||||
|
// indices.extend_from_slice(&[base, base + 1, base + 2, base + 1, base + 3, base + 2]);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // === Bottom Cap Vertices (Separate) ===
|
||||||
|
// let bottom_start = positions.len() as u32;
|
||||||
|
// for i in 0..resolution {
|
||||||
|
// let theta = i as f32 / resolution as f32 * 2.0 * PI;
|
||||||
|
// let (cos, sin) = (theta.cos(), theta.sin());
|
||||||
|
// positions.push([bottom_radius * cos, 0.0, bottom_radius * sin]);
|
||||||
|
// normals.push([0.0, -1.0, 0.0]);
|
||||||
|
// uvs.push([0.5 + 0.5 * cos, 0.5 + 0.5 * sin]);
|
||||||
|
// }
|
||||||
|
// let bottom_center_index = positions.len() as u32;
|
||||||
|
// positions.push([0.0, 0.0, 0.0]);
|
||||||
|
// normals.push([0.0, -1.0, 0.0]);
|
||||||
|
// uvs.push([0.5, 0.5]);
|
||||||
|
|
||||||
|
// for i in 0..resolution {
|
||||||
|
// let current = bottom_start + i as u32;
|
||||||
|
// let next = bottom_start + ((i + 1) % resolution) as u32;
|
||||||
|
// indices.extend_from_slice(&[bottom_center_index, current, next]);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // === Top Cap Vertices (Separate) ===
|
||||||
|
// let top_start = positions.len() as u32;
|
||||||
|
// for i in 0..resolution {
|
||||||
|
// let theta = i as f32 / resolution as f32 * 2.0 * PI;
|
||||||
|
// let (cos, sin) = (theta.cos(), theta.sin());
|
||||||
|
// positions.push([top_radius * cos, height, top_radius * sin]);
|
||||||
|
// normals.push([0.0, 1.0, 0.0]);
|
||||||
|
// uvs.push([0.5 + 0.5 * cos, 0.5 + 0.5 * sin]);
|
||||||
|
// }
|
||||||
|
// let top_center_index = positions.len() as u32;
|
||||||
|
// positions.push([0.0, height, 0.0]);
|
||||||
|
// normals.push([0.0, 1.0, 0.0]);
|
||||||
|
// uvs.push([0.5, 0.5]);
|
||||||
|
|
||||||
|
// for i in 0..resolution {
|
||||||
|
// let current = top_start + i as u32;
|
||||||
|
// let next = top_start + ((i + 1) % resolution) as u32;
|
||||||
|
// indices.extend_from_slice(&[top_center_index, next, current]); // correct winding
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Mesh::new(
|
||||||
|
// PrimitiveTopology::TriangleList,
|
||||||
|
// RenderAssetUsages::MAIN_WORLD | RenderAssetUsages::RENDER_WORLD,
|
||||||
|
// )
|
||||||
|
// .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)
|
||||||
|
// .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)
|
||||||
|
// .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)
|
||||||
|
// .with_inserted_indices(Indices::U32(indices))
|
||||||
|
// }
|
||||||
|
|
||||||
|
pub struct ConeSegment {
|
||||||
|
pub radius: f32,
|
||||||
|
pub offset: f32,
|
||||||
|
pub rotation: f32,
|
||||||
|
pub angle: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn append_connected_truncated_cones_to_mesh(
|
||||||
|
mesh: &mut Mesh,
|
||||||
|
segments: &[ConeSegment],
|
||||||
|
resolution: usize,
|
||||||
|
) {
|
||||||
|
use bevy::math::Vec3;
|
||||||
|
use bevy::render::mesh::{Indices, VertexAttributeValues};
|
||||||
|
use std::f32::consts::PI;
|
||||||
|
|
||||||
|
let verts_per_ring = resolution + 1;
|
||||||
|
|
||||||
|
let base_index = match mesh.attribute(Mesh::ATTRIBUTE_POSITION) {
|
||||||
|
Some(VertexAttributeValues::Float32x3(v)) => v.len() as u32,
|
||||||
|
_ => 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut new_positions = Vec::new();
|
||||||
|
let mut new_normals: Vec<[f32; 3]> = Vec::new();
|
||||||
|
let mut new_uvs = Vec::new();
|
||||||
|
let mut new_indices = Vec::new();
|
||||||
|
|
||||||
|
let mut transform = Transform::IDENTITY;
|
||||||
|
|
||||||
|
for (i, seg) in segments.iter().enumerate() {
|
||||||
|
let prev_segment = if i != 0 { segments.get(i - 1) } else { None };
|
||||||
|
let next_segment = segments.get(i + 1);
|
||||||
|
|
||||||
|
let prev_y_normal = prev_segment.map(|p| (p.radius - seg.radius) / seg.offset);
|
||||||
|
let next_y_normal = next_segment.map(|n| (seg.radius - n.radius) / n.offset);
|
||||||
|
|
||||||
|
let segment_y_normal = match (prev_y_normal, next_y_normal) {
|
||||||
|
(Some(prev), Some(next)) => (prev + next) / 2.,
|
||||||
|
(None, Some(next)) => next,
|
||||||
|
(Some(prev), None) => prev,
|
||||||
|
(None, None) => 0.,
|
||||||
|
};
|
||||||
|
|
||||||
|
transform.rotate_local_y(seg.rotation);
|
||||||
|
transform.rotate_local_x(seg.angle);
|
||||||
|
transform.rotate_local_y(-seg.rotation);
|
||||||
|
transform.translation += transform.up() * seg.offset;
|
||||||
|
|
||||||
|
for j in 0..=resolution {
|
||||||
|
let theta = j as f32 / resolution as f32 * 2.0 * PI;
|
||||||
|
let (cos, sin) = (theta.cos(), theta.sin());
|
||||||
|
|
||||||
|
let normal = Vec3::new(cos, segment_y_normal, sin).normalize();
|
||||||
|
let normal = transform.rotation * normal;
|
||||||
|
// info!("normal: {}", normal);
|
||||||
|
let position =
|
||||||
|
transform.transform_point(Vec3::new(seg.radius * cos, 0., seg.radius * sin));
|
||||||
|
|
||||||
|
new_positions.push(position.to_array());
|
||||||
|
new_normals.push(normal.into());
|
||||||
|
new_uvs.push([
|
||||||
|
j as f32 / resolution as f32,
|
||||||
|
i as f32 / segments.len() as f32,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i in 0..segments.len() - 1 {
|
||||||
|
let ring_start = base_index + (i * verts_per_ring) as u32;
|
||||||
|
for j in 0..resolution {
|
||||||
|
let curr = ring_start + j as u32;
|
||||||
|
let next = curr + 1;
|
||||||
|
let above = curr + verts_per_ring as u32;
|
||||||
|
let above_next = next + verts_per_ring as u32;
|
||||||
|
|
||||||
|
new_indices.extend_from_slice(&[curr, above, next, next, above, above_next]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let top_point = transform.transform_point(Vec3::ZERO);
|
||||||
|
let top_normal = transform.rotation * Vec3::new(0.0, 1.0, 0.0);
|
||||||
|
let cap_top_index = new_positions.len() as u32;
|
||||||
|
let top_ring_start =
|
||||||
|
base_index + (segments.len() * verts_per_ring) as u32 - verts_per_ring as u32;
|
||||||
|
|
||||||
|
new_positions.push(top_point.to_array());
|
||||||
|
new_normals.push(top_normal.to_array());
|
||||||
|
new_uvs.push([0.5, 0.5]);
|
||||||
|
|
||||||
|
for j in 0..resolution {
|
||||||
|
let i0 = top_ring_start + j as u32;
|
||||||
|
let i1 = top_ring_start + ((j + 1) % resolution) as u32;
|
||||||
|
new_indices.extend_from_slice(&[cap_top_index, i1, i0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
match mesh.attribute_mut(Mesh::ATTRIBUTE_POSITION) {
|
||||||
|
Some(VertexAttributeValues::Float32x3(v)) => v.extend(new_positions),
|
||||||
|
_ => {
|
||||||
|
mesh.insert_attribute(
|
||||||
|
Mesh::ATTRIBUTE_POSITION,
|
||||||
|
VertexAttributeValues::Float32x3(new_positions),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match mesh.attribute_mut(Mesh::ATTRIBUTE_NORMAL) {
|
||||||
|
Some(VertexAttributeValues::Float32x3(v)) => v.extend(new_normals),
|
||||||
|
_ => {
|
||||||
|
mesh.insert_attribute(
|
||||||
|
Mesh::ATTRIBUTE_NORMAL,
|
||||||
|
VertexAttributeValues::Float32x3(new_normals),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match mesh.attribute_mut(Mesh::ATTRIBUTE_UV_0) {
|
||||||
|
Some(VertexAttributeValues::Float32x2(v)) => v.extend(new_uvs),
|
||||||
|
_ => {
|
||||||
|
mesh.insert_attribute(
|
||||||
|
Mesh::ATTRIBUTE_UV_0,
|
||||||
|
VertexAttributeValues::Float32x2(new_uvs),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match mesh.indices_mut() {
|
||||||
|
Some(Indices::U32(i)) => i.extend(new_indices),
|
||||||
|
_ => {
|
||||||
|
mesh.insert_indices(Indices::U32(new_indices));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
272
src/tree_rendering.rs
Normal file
272
src/tree_rendering.rs
Normal file
@ -0,0 +1,272 @@
|
|||||||
|
use crate::tree::{Tree, TreeNode, TreeNodeKind};
|
||||||
|
use bevy::{
|
||||||
|
asset::RenderAssetUsages,
|
||||||
|
prelude::*,
|
||||||
|
render::mesh::{self, Indices, PrimitiveTopology},
|
||||||
|
};
|
||||||
|
use std::f32::consts::PI;
|
||||||
|
|
||||||
|
const RESOLUTION: usize = 10;
|
||||||
|
|
||||||
|
struct TreeMeshData {
|
||||||
|
positions: Vec<[f32; 3]>,
|
||||||
|
normals: Vec<[f32; 3]>,
|
||||||
|
uvs: Vec<[f32; 2]>,
|
||||||
|
indices: Vec<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for TreeMeshData {
|
||||||
|
fn default() -> Self {
|
||||||
|
TreeMeshData {
|
||||||
|
positions: Vec::new(),
|
||||||
|
normals: Vec::new(),
|
||||||
|
uvs: Vec::new(),
|
||||||
|
indices: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TreeMeshData {
|
||||||
|
fn to_mesh(self) -> Mesh {
|
||||||
|
Mesh::new(
|
||||||
|
PrimitiveTopology::TriangleList,
|
||||||
|
RenderAssetUsages::MAIN_WORLD | RenderAssetUsages::RENDER_WORLD,
|
||||||
|
)
|
||||||
|
.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, self.positions)
|
||||||
|
.with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, self.normals)
|
||||||
|
.with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, self.uvs)
|
||||||
|
.with_inserted_indices(Indices::U32(self.indices))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate_tree(tree: &Tree) -> Mesh {
|
||||||
|
let mut mesh = TreeMeshData::default().to_mesh();
|
||||||
|
info!("{:?}", tree.nodes);
|
||||||
|
|
||||||
|
let mut branches = Vec::new();
|
||||||
|
for (id, node) in tree.nodes.iter().filter(|(_, n)| n.is_end()) {
|
||||||
|
let mut curr_node = node;
|
||||||
|
loop {
|
||||||
|
info!("{:?}", curr_node);
|
||||||
|
branches.push(curr_node.clone());
|
||||||
|
if curr_node.id == curr_node.parent_id {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let next_node = &tree.nodes[&curr_node.parent_id];
|
||||||
|
if curr_node.is_branch_begin() {
|
||||||
|
let mut modified_node = next_node.clone();
|
||||||
|
modified_node.offset = 0.;
|
||||||
|
modified_node.angle = 0.;
|
||||||
|
modified_node.rotation = 0.;
|
||||||
|
modified_node.radius = curr_node.radius*1.1;
|
||||||
|
branches.push(modified_node);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
curr_node = next_node;
|
||||||
|
}
|
||||||
|
branches.reverse();
|
||||||
|
info!("{:?}", branches);
|
||||||
|
let mut branch_mesh = generate_branch_mesh(branches.as_slice());
|
||||||
|
let transform = tree.get_accumulated_transform(branches[0].id);
|
||||||
|
info!("{:?}", transform);
|
||||||
|
branch_mesh.transform_by(transform);
|
||||||
|
mesh.merge(&branch_mesh).unwrap(); // TODO: unwrap
|
||||||
|
branches.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
mesh
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate_branch_mesh(nodes: &[TreeNode]) -> Mesh {
|
||||||
|
let mut mesh_data = TreeMeshData::default();
|
||||||
|
|
||||||
|
let mut transform = Transform::IDENTITY;
|
||||||
|
|
||||||
|
for (i, node) in nodes.iter().enumerate() {
|
||||||
|
let prev_node = if i != 0 { nodes.get(i - 1) } else { None };
|
||||||
|
let next_node = nodes.get(i + 1);
|
||||||
|
|
||||||
|
let prev_y_normal = prev_node.map(|p| (p.radius - node.radius) / node.offset);
|
||||||
|
let next_y_normal = next_node.map(|n| (node.radius - n.radius) / n.offset);
|
||||||
|
|
||||||
|
// TODO: this normal is wrong (at least not including angle)
|
||||||
|
let segment_y_normal = match (prev_y_normal, next_y_normal) {
|
||||||
|
(Some(prev), Some(next)) => (prev + next) / 2.,
|
||||||
|
(None, Some(next)) => next,
|
||||||
|
(Some(prev), None) => prev,
|
||||||
|
(None, None) => 0.,
|
||||||
|
};
|
||||||
|
transform = transform * node.get_transform();
|
||||||
|
// transform.rotate_local_y(seg.rotation);
|
||||||
|
// transform.rotate_local_x(seg.angle);
|
||||||
|
// transform.rotate_local_y(-seg.rotation);
|
||||||
|
// transform.translation += transform.up() * seg.offset;
|
||||||
|
|
||||||
|
add_circle_points(
|
||||||
|
&mut mesh_data,
|
||||||
|
RESOLUTION,
|
||||||
|
transform,
|
||||||
|
node.radius,
|
||||||
|
Some(segment_y_normal),
|
||||||
|
);
|
||||||
|
|
||||||
|
if i != 0 {
|
||||||
|
let current_circle_start_index = mesh_data.positions.len() - (RESOLUTION + 1);
|
||||||
|
let prev_circle_start_index = current_circle_start_index - (RESOLUTION + 1);
|
||||||
|
connect_circles(
|
||||||
|
&mut mesh_data,
|
||||||
|
RESOLUTION,
|
||||||
|
prev_circle_start_index as u32,
|
||||||
|
current_circle_start_index as u32,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// for i in 0..nodes.len() - 1 {
|
||||||
|
// let ring_start = (i * verts_per_ring) as u32;
|
||||||
|
// for j in 0..RESOLUTION {
|
||||||
|
// let curr = ring_start + j as u32;
|
||||||
|
// let next = curr + 1;
|
||||||
|
// let above = curr + verts_per_ring as u32;
|
||||||
|
// let above_next = next + verts_per_ring as u32;
|
||||||
|
|
||||||
|
// mesh_data
|
||||||
|
// .indices
|
||||||
|
// .extend_from_slice(&[curr, above, next, next, above, above_next]);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// match nodes.last() {
|
||||||
|
// Some(node) => {
|
||||||
|
// let top_point = transform.transform_point(Vec3::ZERO);
|
||||||
|
// let top_normal = transform.rotation * Vec3::new(0.0, 1.0, 0.0);
|
||||||
|
// let cap_top_index = mesh_data.positions.len() as u32;
|
||||||
|
// let top_ring_start = (nodes.len() * verts_per_ring) as u32 - verts_per_ring as u32;
|
||||||
|
|
||||||
|
// mesh_data.positions.push(top_point.to_array());
|
||||||
|
// mesh_data.normals.push(top_normal.to_array());
|
||||||
|
// mesh_data.uvs.push([0.5, 0.5]);
|
||||||
|
|
||||||
|
// match node.kind {
|
||||||
|
// TreeNodeKind::EndMeristem => {}
|
||||||
|
// TreeNodeKind::EndCut => {}
|
||||||
|
// _ => {}
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// _ => {}
|
||||||
|
// }
|
||||||
|
|
||||||
|
if let Some(node) = nodes.last() {
|
||||||
|
let last_circle_index = match node.kind {
|
||||||
|
TreeNodeKind::EndMeristem => {
|
||||||
|
Some((mesh_data.positions.len() - (RESOLUTION + 1)) as u32)
|
||||||
|
}
|
||||||
|
TreeNodeKind::EndCut => None,
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
close_circle(
|
||||||
|
&mut mesh_data,
|
||||||
|
RESOLUTION,
|
||||||
|
last_circle_index,
|
||||||
|
transform,
|
||||||
|
node.radius,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// let top_point = transform.transform_point(Vec3::ZERO);
|
||||||
|
// let top_normal = transform.rotation * Vec3::new(0.0, 1.0, 0.0);
|
||||||
|
// let cap_top_index = mesh_data.positions.len() as u32;
|
||||||
|
// let top_ring_start = (nodes.len() * verts_per_ring) as u32 - verts_per_ring as u32;
|
||||||
|
|
||||||
|
// mesh_data.positions.push(top_point.to_array());
|
||||||
|
// mesh_data.normals.push(top_normal.to_array());
|
||||||
|
// mesh_data.uvs.push([0.5, 0.5]);
|
||||||
|
|
||||||
|
// for j in 0..RESOLUTION {
|
||||||
|
// let i0 = top_ring_start + j as u32;
|
||||||
|
// let i1 = top_ring_start + ((j + 1) % RESOLUTION) as u32;
|
||||||
|
// mesh_data
|
||||||
|
// .indices
|
||||||
|
// .extend_from_slice(&[cap_top_index, i1, i0]);
|
||||||
|
// }
|
||||||
|
|
||||||
|
mesh_data.to_mesh()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_circle_points(
|
||||||
|
mesh_data: &mut TreeMeshData,
|
||||||
|
resolution: usize,
|
||||||
|
transform: Transform,
|
||||||
|
radius: f32,
|
||||||
|
local_y_normal: Option<f32>,
|
||||||
|
) {
|
||||||
|
for i in 0..=resolution {
|
||||||
|
let theta = i as f32 / RESOLUTION as f32 * 2.0 * PI;
|
||||||
|
let (cos, sin) = (theta.cos(), theta.sin());
|
||||||
|
|
||||||
|
let normal = if let Some(local_y_normal) = local_y_normal {
|
||||||
|
Vec3::new(cos, local_y_normal, sin).normalize()
|
||||||
|
} else {
|
||||||
|
Vec3::Y
|
||||||
|
};
|
||||||
|
let normal = transform.rotation * normal;
|
||||||
|
let position = transform.transform_point(Vec3::new(radius * cos, 0., radius * sin));
|
||||||
|
|
||||||
|
mesh_data.positions.push(position.to_array());
|
||||||
|
mesh_data.normals.push(normal.into());
|
||||||
|
// TODO: UVs
|
||||||
|
mesh_data.uvs.push([0.5, 0.5]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn connect_circles(
|
||||||
|
mesh_data: &mut TreeMeshData,
|
||||||
|
resolution: usize,
|
||||||
|
index_circle1: u32,
|
||||||
|
index_circle2: u32,
|
||||||
|
) {
|
||||||
|
for i in 0..resolution {
|
||||||
|
let curr = index_circle1 + i as u32;
|
||||||
|
let next = curr + 1;
|
||||||
|
let above = index_circle2 + i as u32;
|
||||||
|
let above_next = above + 1;
|
||||||
|
|
||||||
|
mesh_data
|
||||||
|
.indices
|
||||||
|
.extend_from_slice(&[curr, above, next, next, above, above_next]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn close_circle(
|
||||||
|
mesh_data: &mut TreeMeshData,
|
||||||
|
resolution: usize,
|
||||||
|
circle_index: Option<u32>,
|
||||||
|
transform: Transform,
|
||||||
|
radius: f32,
|
||||||
|
) {
|
||||||
|
let circle_index_start = match circle_index {
|
||||||
|
Some(index) => index,
|
||||||
|
None => {
|
||||||
|
add_circle_points(mesh_data, resolution, transform, radius, None);
|
||||||
|
(mesh_data.positions.len() - (resolution + 1)) as u32
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let center_point = transform.transform_point(Vec3::ZERO);
|
||||||
|
let center_point_index = mesh_data.positions.len() as u32;
|
||||||
|
|
||||||
|
mesh_data.positions.push(center_point.to_array());
|
||||||
|
mesh_data
|
||||||
|
.normals
|
||||||
|
.push((transform.rotation * Vec3::Y).to_array());
|
||||||
|
// TODO: UVs
|
||||||
|
mesh_data.uvs.push([0.5, 0.5]);
|
||||||
|
|
||||||
|
for j in 0..RESOLUTION {
|
||||||
|
let i0 = circle_index_start + j as u32;
|
||||||
|
let i1 = circle_index_start + ((j + 1) % RESOLUTION) as u32;
|
||||||
|
mesh_data
|
||||||
|
.indices
|
||||||
|
.extend_from_slice(&[center_point_index, i1, i0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user