|
| 1 | +use crate::{ |
| 2 | + mesh::{Indices, Mesh}, |
| 3 | + pipeline::PrimitiveTopology, |
| 4 | +}; |
| 5 | +use bevy_math::{Vec2, Vec3}; |
| 6 | + |
| 7 | +/// A cylinder with hemispheres at the top and bottom |
| 8 | +pub struct Capsule { |
| 9 | + /// Radius on the xz plane. |
| 10 | + pub radius: f32, |
| 11 | + /// Number of sections in cylinder between hemispheres. |
| 12 | + pub rings: usize, |
| 13 | + /// Height of the middle cylinder on the y axis, excluding the hemispheres. |
| 14 | + pub depth: f32, |
| 15 | + /// Number of latitudes, distributed by inclination. Must be even. |
| 16 | + pub latitudes: usize, |
| 17 | + /// Number of longitudes, or meridians, distributed by azimuth. |
| 18 | + pub longitudes: usize, |
| 19 | + /// Manner in which UV coordinates are distributed vertically. |
| 20 | + pub uv_profile: CapsuleUvProfile, |
| 21 | +} |
| 22 | +impl Default for Capsule { |
| 23 | + fn default() -> Self { |
| 24 | + Capsule { |
| 25 | + radius: 0.5, |
| 26 | + rings: 0, |
| 27 | + depth: 1.0, |
| 28 | + latitudes: 16, |
| 29 | + longitudes: 32, |
| 30 | + uv_profile: CapsuleUvProfile::Aspect, |
| 31 | + } |
| 32 | + } |
| 33 | +} |
| 34 | + |
| 35 | +#[derive(Clone, Copy)] |
| 36 | +/// Manner in which UV coordinates are distributed vertically. |
| 37 | +pub enum CapsuleUvProfile { |
| 38 | + /// UV space is distributed by how much of the capsule consists of the hemispheres. |
| 39 | + Aspect, |
| 40 | + /// Hemispheres get UV space according to the ratio of latitudes to rings. |
| 41 | + Uniform, |
| 42 | + /// Upper third of the texture goes to the northern hemisphere, middle third to the cylinder and lower third to the southern one. |
| 43 | + Fixed, |
| 44 | +} |
| 45 | + |
| 46 | +impl Default for CapsuleUvProfile { |
| 47 | + fn default() -> Self { |
| 48 | + CapsuleUvProfile::Aspect |
| 49 | + } |
| 50 | +} |
| 51 | + |
| 52 | +impl From<Capsule> for Mesh { |
| 53 | + #[allow(clippy::clippy::needless_range_loop)] |
| 54 | + fn from(capsule: Capsule) -> Self { |
| 55 | + // code adapted from https://behreajj.medium.com/making-a-capsule-mesh-via-script-in-five-3d-environments-c2214abf02db |
| 56 | + |
| 57 | + let Capsule { |
| 58 | + radius, |
| 59 | + rings, |
| 60 | + depth, |
| 61 | + latitudes, |
| 62 | + longitudes, |
| 63 | + uv_profile, |
| 64 | + } = capsule; |
| 65 | + |
| 66 | + let calc_middle = rings > 0; |
| 67 | + let half_lats = latitudes / 2; |
| 68 | + let half_latsn1 = half_lats - 1; |
| 69 | + let half_latsn2 = half_lats - 2; |
| 70 | + let ringsp1 = rings + 1; |
| 71 | + let lonsp1 = longitudes + 1; |
| 72 | + let half_depth = depth * 0.5; |
| 73 | + let summit = half_depth + radius; |
| 74 | + |
| 75 | + // Vertex index offsets. |
| 76 | + let vert_offset_north_hemi = longitudes; |
| 77 | + let vert_offset_north_equator = vert_offset_north_hemi + lonsp1 * half_latsn1; |
| 78 | + let vert_offset_cylinder = vert_offset_north_equator + lonsp1; |
| 79 | + let vert_offset_south_equator = if calc_middle { |
| 80 | + vert_offset_cylinder + lonsp1 * rings |
| 81 | + } else { |
| 82 | + vert_offset_cylinder |
| 83 | + }; |
| 84 | + let vert_offset_south_hemi = vert_offset_south_equator + lonsp1; |
| 85 | + let vert_offset_south_polar = vert_offset_south_hemi + lonsp1 * half_latsn2; |
| 86 | + let vert_offset_south_cap = vert_offset_south_polar + lonsp1; |
| 87 | + |
| 88 | + // Initialize arrays. |
| 89 | + let vert_len = vert_offset_south_cap + longitudes; |
| 90 | + |
| 91 | + let mut vs: Vec<Vec3> = vec![Vec3::default(); vert_len]; |
| 92 | + let mut vts: Vec<Vec2> = vec![Vec2::default(); vert_len]; |
| 93 | + let mut vns: Vec<Vec3> = vec![Vec3::default(); vert_len]; |
| 94 | + |
| 95 | + let to_theta = 2.0 * std::f32::consts::PI / longitudes as f32; |
| 96 | + let to_phi = std::f32::consts::PI / latitudes as f32; |
| 97 | + let to_tex_horizontal = 1.0 / longitudes as f32; |
| 98 | + let to_tex_vertical = 1.0 / half_lats as f32; |
| 99 | + |
| 100 | + let vt_aspect_ratio = match uv_profile { |
| 101 | + CapsuleUvProfile::Aspect => radius / (depth + radius + radius), |
| 102 | + CapsuleUvProfile::Uniform => half_lats as f32 / (ringsp1 + latitudes) as f32, |
| 103 | + CapsuleUvProfile::Fixed => 1.0 / 3.0, |
| 104 | + }; |
| 105 | + let vt_aspect_north = 1.0 - vt_aspect_ratio; |
| 106 | + let vt_aspect_south = vt_aspect_ratio; |
| 107 | + |
| 108 | + let mut theta_cartesian: Vec<Vec2> = vec![Vec2::default(); longitudes]; |
| 109 | + let mut rho_theta_cartesian: Vec<Vec2> = vec![Vec2::default(); longitudes]; |
| 110 | + let mut s_texture_cache: Vec<f32> = vec![0.0; lonsp1]; |
| 111 | + |
| 112 | + for j in 0..longitudes { |
| 113 | + let jf = j as f32; |
| 114 | + let s_texture_polar = 1.0 - ((jf + 0.5) * to_tex_horizontal); |
| 115 | + let theta = jf * to_theta; |
| 116 | + |
| 117 | + let cos_theta = theta.cos(); |
| 118 | + let sin_theta = theta.sin(); |
| 119 | + |
| 120 | + theta_cartesian[j] = Vec2::new(cos_theta, sin_theta); |
| 121 | + rho_theta_cartesian[j] = Vec2::new(radius * cos_theta, radius * sin_theta); |
| 122 | + |
| 123 | + // North. |
| 124 | + vs[j] = Vec3::new(0.0, summit, 0.0); |
| 125 | + vts[j] = Vec2::new(s_texture_polar, 1.0); |
| 126 | + vns[j] = Vec3::new(0.0, 1.0, 0.0); |
| 127 | + |
| 128 | + // South. |
| 129 | + let idx = vert_offset_south_cap + j; |
| 130 | + vs[idx] = Vec3::new(0.0, -summit, 0.0); |
| 131 | + vts[idx] = Vec2::new(s_texture_polar, 0.0); |
| 132 | + vns[idx] = Vec3::new(0.0, -1.0, 0.0); |
| 133 | + } |
| 134 | + |
| 135 | + // Equatorial vertices. |
| 136 | + for j in 0..lonsp1 { |
| 137 | + let s_texture = 1.0 - j as f32 * to_tex_horizontal; |
| 138 | + s_texture_cache[j] = s_texture; |
| 139 | + |
| 140 | + // Wrap to first element upon reaching last. |
| 141 | + let j_mod = j % longitudes; |
| 142 | + let tc = theta_cartesian[j_mod]; |
| 143 | + let rtc = rho_theta_cartesian[j_mod]; |
| 144 | + |
| 145 | + // North equator. |
| 146 | + let idxn = vert_offset_north_equator + j; |
| 147 | + vs[idxn] = Vec3::new(rtc.x, half_depth, -rtc.y); |
| 148 | + vts[idxn] = Vec2::new(s_texture, vt_aspect_north); |
| 149 | + vns[idxn] = Vec3::new(tc.x, 0.0, -tc.y); |
| 150 | + |
| 151 | + // South equator. |
| 152 | + let idxs = vert_offset_south_equator + j; |
| 153 | + vs[idxs] = Vec3::new(rtc.x, -half_depth, -rtc.y); |
| 154 | + vts[idxs] = Vec2::new(s_texture, vt_aspect_south); |
| 155 | + vns[idxs] = Vec3::new(tc.x, 0.0, -tc.y); |
| 156 | + } |
| 157 | + |
| 158 | + // Hemisphere vertices. |
| 159 | + for i in 0..half_latsn1 { |
| 160 | + let ip1f = i as f32 + 1.0; |
| 161 | + let phi = ip1f * to_phi; |
| 162 | + |
| 163 | + // For coordinates. |
| 164 | + let cos_phi_south = phi.cos(); |
| 165 | + let sin_phi_south = phi.sin(); |
| 166 | + |
| 167 | + // Symmetrical hemispheres mean cosine and sine only needs |
| 168 | + // to be calculated once. |
| 169 | + let cos_phi_north = sin_phi_south; |
| 170 | + let sin_phi_north = -cos_phi_south; |
| 171 | + |
| 172 | + let rho_cos_phi_north = radius * cos_phi_north; |
| 173 | + let rho_sin_phi_north = radius * sin_phi_north; |
| 174 | + let z_offset_north = half_depth - rho_sin_phi_north; |
| 175 | + |
| 176 | + let rho_cos_phi_south = radius * cos_phi_south; |
| 177 | + let rho_sin_phi_south = radius * sin_phi_south; |
| 178 | + let z_offset_sout = -half_depth - rho_sin_phi_south; |
| 179 | + |
| 180 | + // For texture coordinates. |
| 181 | + let t_tex_fac = ip1f * to_tex_vertical; |
| 182 | + let cmpl_tex_fac = 1.0 - t_tex_fac; |
| 183 | + let t_tex_north = cmpl_tex_fac + vt_aspect_north * t_tex_fac; |
| 184 | + let t_tex_south = cmpl_tex_fac * vt_aspect_south; |
| 185 | + |
| 186 | + let i_lonsp1 = i * lonsp1; |
| 187 | + let vert_curr_lat_north = vert_offset_north_hemi + i_lonsp1; |
| 188 | + let vert_curr_lat_south = vert_offset_south_hemi + i_lonsp1; |
| 189 | + |
| 190 | + for j in 0..lonsp1 { |
| 191 | + let j_mod = j % longitudes; |
| 192 | + |
| 193 | + let s_texture = s_texture_cache[j]; |
| 194 | + let tc = theta_cartesian[j_mod]; |
| 195 | + |
| 196 | + // North hemisphere. |
| 197 | + let idxn = vert_curr_lat_north + j; |
| 198 | + vs[idxn] = Vec3::new( |
| 199 | + rho_cos_phi_north * tc.x, |
| 200 | + z_offset_north, |
| 201 | + -rho_cos_phi_north * tc.y, |
| 202 | + ); |
| 203 | + vts[idxn] = Vec2::new(s_texture, t_tex_north); |
| 204 | + vns[idxn] = Vec3::new(cos_phi_north * tc.x, -sin_phi_north, -cos_phi_north * tc.y); |
| 205 | + |
| 206 | + // South hemisphere. |
| 207 | + let idxs = vert_curr_lat_south + j; |
| 208 | + vs[idxs] = Vec3::new( |
| 209 | + rho_cos_phi_south * tc.x, |
| 210 | + z_offset_sout, |
| 211 | + -rho_cos_phi_south * tc.y, |
| 212 | + ); |
| 213 | + vts[idxs] = Vec2::new(s_texture, t_tex_south); |
| 214 | + vns[idxs] = Vec3::new(cos_phi_south * tc.x, -sin_phi_south, -cos_phi_south * tc.y); |
| 215 | + } |
| 216 | + } |
| 217 | + |
| 218 | + // Cylinder vertices. |
| 219 | + if calc_middle { |
| 220 | + // Exclude both origin and destination edges |
| 221 | + // (North and South equators) from the interpolation. |
| 222 | + let to_fac = 1.0 / ringsp1 as f32; |
| 223 | + let mut idx_cyl_lat = vert_offset_cylinder; |
| 224 | + |
| 225 | + for h in 1..ringsp1 { |
| 226 | + let fac = h as f32 * to_fac; |
| 227 | + let cmpl_fac = 1.0 - fac; |
| 228 | + let t_texture = cmpl_fac * vt_aspect_north + fac * vt_aspect_south; |
| 229 | + let z = half_depth - depth * fac; |
| 230 | + |
| 231 | + for j in 0..lonsp1 { |
| 232 | + let j_mod = j % longitudes; |
| 233 | + let tc = theta_cartesian[j_mod]; |
| 234 | + let rtc = rho_theta_cartesian[j_mod]; |
| 235 | + let s_texture = s_texture_cache[j]; |
| 236 | + |
| 237 | + vs[idx_cyl_lat] = Vec3::new(rtc.x, z, -rtc.y); |
| 238 | + vts[idx_cyl_lat] = Vec2::new(s_texture, t_texture); |
| 239 | + vns[idx_cyl_lat] = Vec3::new(tc.x, 0.0, -tc.y); |
| 240 | + |
| 241 | + idx_cyl_lat += 1; |
| 242 | + } |
| 243 | + } |
| 244 | + } |
| 245 | + |
| 246 | + // Triangle indices. |
| 247 | + |
| 248 | + // Stride is 3 for polar triangles; |
| 249 | + // stride is 6 for two triangles forming a quad. |
| 250 | + let lons3 = longitudes * 3; |
| 251 | + let lons6 = longitudes * 6; |
| 252 | + let hemi_lons = half_latsn1 * lons6; |
| 253 | + |
| 254 | + let tri_offset_north_hemi = lons3; |
| 255 | + let tri_offset_cylinder = tri_offset_north_hemi + hemi_lons; |
| 256 | + let tri_offset_south_hemi = tri_offset_cylinder + ringsp1 * lons6; |
| 257 | + let tri_offset_south_cap = tri_offset_south_hemi + hemi_lons; |
| 258 | + |
| 259 | + let fs_len = tri_offset_south_cap + lons3; |
| 260 | + let mut tris: Vec<u32> = vec![0; fs_len]; |
| 261 | + |
| 262 | + // Polar caps. |
| 263 | + let mut i = 0; |
| 264 | + let mut k = 0; |
| 265 | + let mut m = tri_offset_south_cap; |
| 266 | + while i < longitudes { |
| 267 | + // North. |
| 268 | + tris[k] = i as u32; |
| 269 | + tris[k + 1] = (vert_offset_north_hemi + i) as u32; |
| 270 | + tris[k + 2] = (vert_offset_north_hemi + i + 1) as u32; |
| 271 | + |
| 272 | + // South. |
| 273 | + tris[m] = (vert_offset_south_cap + i) as u32; |
| 274 | + tris[m + 1] = (vert_offset_south_polar + i + 1) as u32; |
| 275 | + tris[m + 2] = (vert_offset_south_polar + i) as u32; |
| 276 | + |
| 277 | + i += 1; |
| 278 | + k += 3; |
| 279 | + m += 3; |
| 280 | + } |
| 281 | + |
| 282 | + // Hemispheres. |
| 283 | + |
| 284 | + let mut i = 0; |
| 285 | + let mut k = tri_offset_north_hemi; |
| 286 | + let mut m = tri_offset_south_hemi; |
| 287 | + |
| 288 | + while i < half_latsn1 { |
| 289 | + let i_lonsp1 = i * lonsp1; |
| 290 | + |
| 291 | + let vert_curr_lat_north = vert_offset_north_hemi + i_lonsp1; |
| 292 | + let vert_next_lat_north = vert_curr_lat_north + lonsp1; |
| 293 | + |
| 294 | + let vert_curr_lat_south = vert_offset_south_equator + i_lonsp1; |
| 295 | + let vert_next_lat_south = vert_curr_lat_south + lonsp1; |
| 296 | + |
| 297 | + let mut j = 0; |
| 298 | + while j < longitudes { |
| 299 | + // North. |
| 300 | + let north00 = vert_curr_lat_north + j; |
| 301 | + let north01 = vert_next_lat_north + j; |
| 302 | + let north11 = vert_next_lat_north + j + 1; |
| 303 | + let north10 = vert_curr_lat_north + j + 1; |
| 304 | + |
| 305 | + tris[k] = north00 as u32; |
| 306 | + tris[k + 1] = north11 as u32; |
| 307 | + tris[k + 2] = north10 as u32; |
| 308 | + |
| 309 | + tris[k + 3] = north00 as u32; |
| 310 | + tris[k + 4] = north01 as u32; |
| 311 | + tris[k + 5] = north11 as u32; |
| 312 | + |
| 313 | + // South. |
| 314 | + let south00 = vert_curr_lat_south + j; |
| 315 | + let south01 = vert_next_lat_south + j; |
| 316 | + let south11 = vert_next_lat_south + j + 1; |
| 317 | + let south10 = vert_curr_lat_south + j + 1; |
| 318 | + |
| 319 | + tris[m] = south00 as u32; |
| 320 | + tris[m + 1] = south11 as u32; |
| 321 | + tris[m + 2] = south10 as u32; |
| 322 | + |
| 323 | + tris[m + 3] = south00 as u32; |
| 324 | + tris[m + 4] = south01 as u32; |
| 325 | + tris[m + 5] = south11 as u32; |
| 326 | + |
| 327 | + j += 1; |
| 328 | + k += 6; |
| 329 | + m += 6; |
| 330 | + } |
| 331 | + |
| 332 | + i += 1; |
| 333 | + } |
| 334 | + |
| 335 | + // Cylinder. |
| 336 | + let mut i = 0; |
| 337 | + let mut k = tri_offset_cylinder; |
| 338 | + |
| 339 | + while i < ringsp1 { |
| 340 | + let vert_curr_lat = vert_offset_north_equator + i * lonsp1; |
| 341 | + let vert_next_lat = vert_curr_lat + lonsp1; |
| 342 | + |
| 343 | + let mut j = 0; |
| 344 | + while j < longitudes { |
| 345 | + let cy00 = vert_curr_lat + j; |
| 346 | + let cy01 = vert_next_lat + j; |
| 347 | + let cy11 = vert_next_lat + j + 1; |
| 348 | + let cy10 = vert_curr_lat + j + 1; |
| 349 | + |
| 350 | + tris[k] = cy00 as u32; |
| 351 | + tris[k + 1] = cy11 as u32; |
| 352 | + tris[k + 2] = cy10 as u32; |
| 353 | + |
| 354 | + tris[k + 3] = cy00 as u32; |
| 355 | + tris[k + 4] = cy01 as u32; |
| 356 | + tris[k + 5] = cy11 as u32; |
| 357 | + |
| 358 | + j += 1; |
| 359 | + k += 6; |
| 360 | + } |
| 361 | + |
| 362 | + i += 1; |
| 363 | + } |
| 364 | + |
| 365 | + let vs: Vec<[f32; 3]> = vs.into_iter().map(Into::into).collect(); |
| 366 | + let vns: Vec<[f32; 3]> = vns.into_iter().map(Into::into).collect(); |
| 367 | + let vts: Vec<[f32; 2]> = vts.into_iter().map(Into::into).collect(); |
| 368 | + |
| 369 | + assert_eq!(vs.len(), vert_len); |
| 370 | + assert_eq!(tris.len(), fs_len); |
| 371 | + |
| 372 | + let mut mesh = Mesh::new(PrimitiveTopology::TriangleList); |
| 373 | + mesh.set_attribute(Mesh::ATTRIBUTE_POSITION, vs); |
| 374 | + mesh.set_attribute(Mesh::ATTRIBUTE_NORMAL, vns); |
| 375 | + mesh.set_attribute(Mesh::ATTRIBUTE_UV_0, vts); |
| 376 | + mesh.set_indices(Some(Indices::U32(tris))); |
| 377 | + mesh |
| 378 | + } |
| 379 | +} |
0 commit comments