Skip to content

Commit 3f2dd22

Browse files
bevy_render: add torus and capsule shape (#1223)
* bevy_render: add torus shape * bevy_render: add capsule shape * bevy_render: reorganize shape module * bevy_render: add more docs
1 parent 5e74561 commit 3f2dd22

File tree

4 files changed

+585
-72
lines changed

4 files changed

+585
-72
lines changed
Lines changed: 379 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,379 @@
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

Comments
 (0)