better tree rendering

This commit is contained in:
Evan Peterson 2025-05-31 17:04:27 -04:00
parent 33901a170d
commit 23bac13a1e
Signed by: petersonev
GPG Key ID: 26BC6134519C4FC6
4 changed files with 176 additions and 642 deletions

View File

@ -1,15 +1,15 @@
use std::f32::consts::PI; use std::f32::consts::PI;
use bevy::asset::RenderAssetUsages; use bevy::asset::RenderAssetUsages;
use bevy::pbr::wireframe::{WireframeConfig, WireframePlugin};
use bevy::prelude::*; use bevy::prelude::*;
use bevy::render::mesh::{PrimitiveTopology, VertexAttributeValues}; use bevy::render::mesh::{PrimitiveTopology, VertexAttributeValues};
use bevy::window::PresentMode;
use tree::{ConeSegment, TreeNode};
mod camera; mod camera;
mod debugging; mod debugging;
mod tree; mod tree;
mod tree_rendering; mod tree_rendering;
mod tree_generation;
fn main() { fn main() {
App::new() App::new()
@ -17,6 +17,11 @@ fn main() {
.add_plugins(debugging::DebuggingPlugin) .add_plugins(debugging::DebuggingPlugin)
.add_plugins(camera::CameraPlugin) .add_plugins(camera::CameraPlugin)
.add_systems(Startup, setup) .add_systems(Startup, setup)
// .add_plugins(WireframePlugin::default())
// .insert_resource(WireframeConfig {
// default_color: Color::BLACK,
// global: true, // or false if you want per-object control
// })
.run(); .run();
} }
@ -64,230 +69,29 @@ fn setup(
rotation: Quat::from_rotation_x(-PI / 4.) * Quat::from_rotation_y(-PI / 4.), rotation: Quat::from_rotation_x(-PI / 4.) * Quat::from_rotation_y(-PI / 4.),
..default() ..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 = initial_tree();
let tree_mesh = tree_rendering::generate_tree(&tree); let tree_mesh = tree_rendering::generate_tree(&tree);
// 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(( commands.spawn((
tree,
Mesh3d(meshes.add(tree_mesh)), Mesh3d(meshes.add(tree_mesh)),
MeshMaterial3d(materials.add(Color::srgb_u8(124, 144, 255))), MeshMaterial3d(materials.add(Color::srgb_u8(124, 144, 255))),
// MeshMaterial3d(materials.add(StandardMaterial {
// base_color: Color::srgba_u8(124, 144, 255, 255),
// alpha_mode: AlphaMode::Blend,
// ..default()
// })),
Transform::from_xyz(0.0, 0.5, 0.0), Transform::from_xyz(0.0, 0.5, 0.0),
)); ));
} }
@ -325,29 +129,6 @@ fn draw_normals(
} }
} }
// 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 { fn initial_tree() -> tree::Tree {
let mut tree = tree::Tree::default(); let mut tree = tree::Tree::default();
tree.nodes.insert( tree.nodes.insert(
@ -357,7 +138,7 @@ fn initial_tree() -> tree::Tree {
kind: tree::TreeNodeKind::Extension, kind: tree::TreeNodeKind::Extension,
parent_id: 0, parent_id: 0,
radius: 0.5, radius: 0.5,
offset: 0., offset: 2.,
angle: 0., angle: 0.,
rotation: 0., rotation: 0.,
}, },
@ -378,9 +159,9 @@ fn initial_tree() -> tree::Tree {
2, 2,
tree::TreeNode { tree::TreeNode {
id: 2, id: 2,
kind: tree::TreeNodeKind::EndCut, kind: tree::TreeNodeKind::Extension,
parent_id: 1, parent_id: 1,
radius: 0.2, radius: 0.25,
offset: 2., offset: 2.,
angle: -0.4, angle: -0.4,
rotation: PI / 2., rotation: PI / 2.,
@ -390,38 +171,61 @@ fn initial_tree() -> tree::Tree {
3, 3,
tree::TreeNode { tree::TreeNode {
id: 3, id: 3,
kind: tree::TreeNodeKind::StartBranch, kind: tree::TreeNodeKind::EndCut,
parent_id: 1, parent_id: 2,
radius: 0.1, radius: 0.2,
offset: 2., offset: 0.,
angle: -0.6, angle: 0.4,
rotation: -PI / 2., rotation: PI / 2.,
}, },
); );
tree.nodes.insert( tree.nodes.insert(
4, 4,
tree::TreeNode { tree::TreeNode {
id: 4, id: 4,
kind: tree::TreeNodeKind::EndCut, kind: tree::TreeNodeKind::StartBranch,
parent_id: 3, parent_id: 1,
radius: 0.05, radius: 0.2,
offset: 2., offset: 2.,
angle: -0.4, angle: -0.6,
rotation: PI / 2., rotation: -PI / 2.,
}, },
); );
tree.nodes.insert( tree.nodes.insert(
5, 5,
tree::TreeNode { tree::TreeNode {
id: 5, id: 5,
kind: tree::TreeNodeKind::EndBranch, kind: tree::TreeNodeKind::Extension,
parent_id: 3, parent_id: 4,
radius: 0.05, radius: 0.1,
offset: 2., offset: 1.,
angle: -0.4, angle: 0.2,
rotation: -PI / 2., rotation: -PI / 2.,
}, },
); );
tree.nodes.insert(
6,
tree::TreeNode {
id: 6,
kind: tree::TreeNodeKind::EndMeristem,
parent_id: 5,
radius: 0.05,
offset: 0.03,
angle: 0.2,
rotation: -PI / 2.,
},
);
tree.nodes.insert(
7,
tree::TreeNode {
id: 7,
kind: tree::TreeNodeKind::EndBranch,
parent_id: 4,
radius: 0.04,
offset: 0.5,
angle: -0.8,
rotation: PI / 4.,
},
);
tree tree
} }

View File

@ -51,11 +51,12 @@ impl Tree {
let mut transform = Transform::IDENTITY; let mut transform = Transform::IDENTITY;
let mut curr_node = &self.nodes[&id]; let mut curr_node = &self.nodes[&id];
loop { loop {
transform = curr_node.get_transform() * transform;
if curr_node.id == curr_node.parent_id { if curr_node.id == curr_node.parent_id {
break; break;
} }
curr_node = &self.nodes[&curr_node.parent_id]; curr_node = &self.nodes[&curr_node.parent_id];
info!("transform: {:?}", curr_node);
transform = curr_node.get_transform() * transform;
} }
transform transform
} }
@ -84,6 +85,18 @@ impl TreeNode {
transform transform
} }
pub fn get_rotation(&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
}
pub fn get_translation(&self) -> Transform {
Transform::from_translation(Vec3::Y * self.offset)
}
pub fn is_end(&self) -> bool { pub fn is_end(&self) -> bool {
matches!( matches!(
self.kind, self.kind,
@ -106,232 +119,3 @@ impl TreeNode {
) )
} }
} }
// 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));
}
}
}

0
src/tree_generation.rs Normal file
View File

View File

@ -2,7 +2,7 @@ use crate::tree::{Tree, TreeNode, TreeNodeKind};
use bevy::{ use bevy::{
asset::RenderAssetUsages, asset::RenderAssetUsages,
prelude::*, prelude::*,
render::mesh::{self, Indices, PrimitiveTopology}, render::mesh::{Indices, PrimitiveTopology},
}; };
use std::f32::consts::PI; use std::f32::consts::PI;
@ -41,34 +41,24 @@ impl TreeMeshData {
pub fn generate_tree(tree: &Tree) -> Mesh { pub fn generate_tree(tree: &Tree) -> Mesh {
let mut mesh = TreeMeshData::default().to_mesh(); let mut mesh = TreeMeshData::default().to_mesh();
info!("{:?}", tree.nodes);
let mut branches = Vec::new(); let mut branches = Vec::new();
for (id, node) in tree.nodes.iter().filter(|(_, n)| n.is_end()) { for (_, node) in tree.nodes.iter().filter(|(_, n)| n.is_end()) {
let mut curr_node = node; let mut curr_node = node;
loop { loop {
info!("{:?}", curr_node);
branches.push(curr_node.clone()); branches.push(curr_node.clone());
if curr_node.id == curr_node.parent_id { if curr_node.id == curr_node.parent_id {
break; break;
} }
let next_node = &tree.nodes[&curr_node.parent_id];
if curr_node.is_branch_begin() { 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; break;
} }
curr_node = next_node; curr_node = &tree.nodes[&curr_node.parent_id];
} }
branches.reverse(); branches.reverse();
info!("{:?}", branches);
let mut branch_mesh = generate_branch_mesh(branches.as_slice()); let mut branch_mesh = generate_branch_mesh(branches.as_slice());
let transform = tree.get_accumulated_transform(branches[0].id); let transform = tree.get_accumulated_transform(branches[0].id);
info!("{:?}", transform);
branch_mesh.transform_by(transform); branch_mesh.transform_by(transform);
mesh.merge(&branch_mesh).unwrap(); // TODO: unwrap mesh.merge(&branch_mesh).unwrap(); // TODO: unwrap
branches.clear(); branches.clear();
@ -77,119 +67,109 @@ pub fn generate_tree(tree: &Tree) -> Mesh {
mesh mesh
} }
/// Adds node extension to mesh data
///
/// Assumes next node exists with same resolution
fn add_extension(
mesh_data: &mut TreeMeshData,
node: &TreeNode,
_next_node: &TreeNode,
// prev_node: Option<&TreeNode>,
transform: &mut Transform,
) {
*transform = transform.mul_transform(node.get_rotation());
add_circle_points(
mesh_data,
RESOLUTION,
*transform,
node.radius,
Some(0.), // TODO: fix normal
);
let next_circle_start_index = mesh_data.positions.len();
let current_circle_start_index = next_circle_start_index - (RESOLUTION + 1);
connect_circles(
mesh_data,
RESOLUTION,
current_circle_start_index as u32,
next_circle_start_index as u32,
);
*transform = transform.mul_transform(node.get_translation());
}
fn add_end(mesh_data: &mut TreeMeshData, node: &TreeNode, transform: &mut Transform) {
*transform = transform.mul_transform(node.get_rotation());
add_circle_points(
mesh_data,
RESOLUTION,
*transform,
node.radius,
Some(0.), // TODO: fix normal
);
if matches!(node.kind, TreeNodeKind::EndCut) {
add_circle_points(mesh_data, RESOLUTION, *transform, node.radius, None);
} else if node.offset > node.radius {
let new_radius =
node.radius - node.radius * 0.5 * (node.offset - node.radius) / node.offset;
let new_length = node.offset - new_radius;
*transform = transform.mul_transform(Transform::from_translation(Vec3::Y * new_length));
let next_circle_start_index = mesh_data.positions.len();
add_circle_points(
mesh_data,
RESOLUTION,
*transform,
new_radius,
Some(0.), // TODO: fix normal
);
let current_circle_start_index = next_circle_start_index - (RESOLUTION + 1);
connect_circles(
mesh_data,
RESOLUTION,
current_circle_start_index as u32,
next_circle_start_index as u32,
);
*transform = transform.mul_transform(Transform::from_translation(
Vec3::Y * (node.offset - new_length),
));
} else {
*transform = transform.mul_transform(node.get_translation());
}
let circle_index_start = (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());
mesh_data.uvs.push([0.5, 0.5]); // TODO: UVs
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]);
}
}
pub fn generate_branch_mesh(nodes: &[TreeNode]) -> Mesh { pub fn generate_branch_mesh(nodes: &[TreeNode]) -> Mesh {
let mut mesh_data = TreeMeshData::default(); let mut mesh_data = TreeMeshData::default();
let mut transform = Transform::IDENTITY; let mut transform = Transform::IDENTITY;
for (i, node) in nodes.iter().enumerate() { 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 next_node = nodes.get(i + 1);
if node.is_extension() {
let prev_y_normal = prev_node.map(|p| (p.radius - node.radius) / node.offset); let next_node = next_node.unwrap(); // TODO: unwrap
let next_y_normal = next_node.map(|n| (node.radius - n.radius) / n.offset); add_extension(&mut mesh_data, &node, &next_node, &mut transform);
} else if node.is_end() {
// TODO: this normal is wrong (at least not including angle) add_end(&mut mesh_data, &node, &mut transform);
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() mesh_data.to_mesh()
} }
@ -236,37 +216,3 @@ fn connect_circles(
.extend_from_slice(&[curr, above, next, next, above, above_next]); .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]);
}
}