Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_mesh/src/primitives/dim2.rs
6596 views
1
use core::f32::consts::FRAC_PI_2;
2
3
use crate::{primitives::dim3::triangle3d, Indices, Mesh, PerimeterSegment};
4
use bevy_asset::RenderAssetUsages;
5
6
use super::{Extrudable, MeshBuilder, Meshable};
7
use bevy_math::prelude::Polyline2d;
8
use bevy_math::{
9
ops,
10
primitives::{
11
Annulus, Capsule2d, Circle, CircularSector, CircularSegment, ConvexPolygon, Ellipse,
12
Rectangle, RegularPolygon, Rhombus, Segment2d, Triangle2d, Triangle3d, WindingOrder,
13
},
14
FloatExt, Vec2,
15
};
16
use bevy_reflect::prelude::*;
17
use wgpu_types::PrimitiveTopology;
18
19
/// A builder used for creating a [`Mesh`] with a [`Circle`] shape.
20
#[derive(Clone, Copy, Debug, Reflect)]
21
#[reflect(Default, Debug, Clone)]
22
pub struct CircleMeshBuilder {
23
/// The [`Circle`] shape.
24
pub circle: Circle,
25
/// The number of vertices used for the circle mesh.
26
/// The default is `32`.
27
#[doc(alias = "vertices")]
28
pub resolution: u32,
29
}
30
31
impl Default for CircleMeshBuilder {
32
fn default() -> Self {
33
Self {
34
circle: Circle::default(),
35
resolution: 32,
36
}
37
}
38
}
39
40
impl CircleMeshBuilder {
41
/// Creates a new [`CircleMeshBuilder`] from a given radius and vertex count.
42
#[inline]
43
pub const fn new(radius: f32, resolution: u32) -> Self {
44
Self {
45
circle: Circle { radius },
46
resolution,
47
}
48
}
49
50
/// Sets the number of vertices used for the circle mesh.
51
#[inline]
52
#[doc(alias = "vertices")]
53
pub const fn resolution(mut self, resolution: u32) -> Self {
54
self.resolution = resolution;
55
self
56
}
57
}
58
59
impl MeshBuilder for CircleMeshBuilder {
60
fn build(&self) -> Mesh {
61
Ellipse::new(self.circle.radius, self.circle.radius)
62
.mesh()
63
.resolution(self.resolution)
64
.build()
65
}
66
}
67
68
impl Extrudable for CircleMeshBuilder {
69
fn perimeter(&self) -> Vec<PerimeterSegment> {
70
vec![PerimeterSegment::Smooth {
71
first_normal: Vec2::Y,
72
last_normal: Vec2::Y,
73
indices: (0..self.resolution).chain([0]).collect(),
74
}]
75
}
76
}
77
78
impl Meshable for Circle {
79
type Output = CircleMeshBuilder;
80
81
fn mesh(&self) -> Self::Output {
82
CircleMeshBuilder {
83
circle: *self,
84
..Default::default()
85
}
86
}
87
}
88
89
impl From<Circle> for Mesh {
90
fn from(circle: Circle) -> Self {
91
circle.mesh().build()
92
}
93
}
94
95
/// Specifies how to generate UV-mappings for the [`CircularSector`] and [`CircularSegment`] shapes.
96
///
97
/// Currently the only variant is `Mask`, which is good for showing a portion of a texture that includes
98
/// the entire circle, particularly the same texture will be displayed with different fractions of a
99
/// complete circle.
100
///
101
/// It's expected that more will be added in the future, such as a variant that causes the texture to be
102
/// scaled to fit the bounding box of the shape, which would be good for packed textures only including the
103
/// portion of the circle that is needed to display.
104
#[derive(Copy, Clone, Debug, PartialEq, Reflect)]
105
#[reflect(Default, Debug, Clone)]
106
#[non_exhaustive]
107
pub enum CircularMeshUvMode {
108
/// Treats the shape as a mask over a circle of equal size and radius,
109
/// with the center of the circle at the center of the texture.
110
Mask {
111
/// Angle by which to rotate the shape when generating the UV map.
112
angle: f32,
113
},
114
}
115
116
impl Default for CircularMeshUvMode {
117
fn default() -> Self {
118
CircularMeshUvMode::Mask { angle: 0.0 }
119
}
120
}
121
122
/// A builder used for creating a [`Mesh`] with a [`CircularSector`] shape.
123
///
124
/// The resulting mesh will have a UV-map such that the center of the circle is
125
/// at the center of the texture.
126
#[derive(Clone, Debug, Reflect)]
127
#[reflect(Default, Debug, Clone)]
128
pub struct CircularSectorMeshBuilder {
129
/// The sector shape.
130
pub sector: CircularSector,
131
/// The number of vertices used for the arc portion of the sector mesh.
132
/// The default is `32`.
133
#[doc(alias = "vertices")]
134
pub resolution: u32,
135
/// The UV mapping mode
136
pub uv_mode: CircularMeshUvMode,
137
}
138
139
impl Default for CircularSectorMeshBuilder {
140
fn default() -> Self {
141
Self {
142
sector: CircularSector::default(),
143
resolution: 32,
144
uv_mode: CircularMeshUvMode::default(),
145
}
146
}
147
}
148
149
impl CircularSectorMeshBuilder {
150
/// Creates a new [`CircularSectorMeshBuilder`] from a given sector
151
#[inline]
152
pub fn new(sector: CircularSector) -> Self {
153
Self {
154
sector,
155
..Self::default()
156
}
157
}
158
159
/// Sets the number of vertices used for the sector mesh.
160
#[inline]
161
#[doc(alias = "vertices")]
162
pub const fn resolution(mut self, resolution: u32) -> Self {
163
self.resolution = resolution;
164
self
165
}
166
167
/// Sets the uv mode used for the sector mesh
168
#[inline]
169
pub const fn uv_mode(mut self, uv_mode: CircularMeshUvMode) -> Self {
170
self.uv_mode = uv_mode;
171
self
172
}
173
}
174
175
impl MeshBuilder for CircularSectorMeshBuilder {
176
fn build(&self) -> Mesh {
177
let resolution = self.resolution as usize;
178
let mut indices = Vec::with_capacity((resolution - 1) * 3);
179
let mut positions = Vec::with_capacity(resolution + 1);
180
let normals = vec![[0.0, 0.0, 1.0]; resolution + 1];
181
let mut uvs = Vec::with_capacity(resolution + 1);
182
183
let CircularMeshUvMode::Mask { angle: uv_angle } = self.uv_mode;
184
185
// Push the center of the circle.
186
positions.push([0.0; 3]);
187
uvs.push([0.5; 2]);
188
189
let first_angle = FRAC_PI_2 - self.sector.half_angle();
190
let last_angle = FRAC_PI_2 + self.sector.half_angle();
191
let last_i = (self.resolution - 1) as f32;
192
for i in 0..self.resolution {
193
let angle = f32::lerp(first_angle, last_angle, i as f32 / last_i);
194
195
// Compute the vertex
196
let vertex = self.sector.radius() * Vec2::from_angle(angle);
197
// Compute the UV coordinate by taking the modified angle's unit vector, negating the Y axis, and rescaling and centering it at (0.5, 0.5).
198
// We accomplish the Y axis flip by negating the angle.
199
let uv =
200
Vec2::from_angle(-(angle + uv_angle)).mul_add(Vec2::splat(0.5), Vec2::splat(0.5));
201
202
positions.push([vertex.x, vertex.y, 0.0]);
203
uvs.push([uv.x, uv.y]);
204
}
205
206
for i in 1..self.resolution {
207
// Index 0 is the center.
208
indices.extend_from_slice(&[0, i, i + 1]);
209
}
210
211
Mesh::new(
212
PrimitiveTopology::TriangleList,
213
RenderAssetUsages::default(),
214
)
215
.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)
216
.with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)
217
.with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)
218
.with_inserted_indices(Indices::U32(indices))
219
}
220
}
221
222
impl Extrudable for CircularSectorMeshBuilder {
223
fn perimeter(&self) -> Vec<PerimeterSegment> {
224
let (sin, cos) = ops::sin_cos(self.sector.arc.half_angle);
225
let first_normal = Vec2::new(sin, cos);
226
let last_normal = Vec2::new(-sin, cos);
227
vec![
228
PerimeterSegment::Flat {
229
indices: vec![self.resolution, 0, 1],
230
},
231
PerimeterSegment::Smooth {
232
first_normal,
233
last_normal,
234
indices: (1..=self.resolution).collect(),
235
},
236
]
237
}
238
}
239
240
impl Meshable for CircularSector {
241
type Output = CircularSectorMeshBuilder;
242
243
fn mesh(&self) -> Self::Output {
244
CircularSectorMeshBuilder {
245
sector: *self,
246
..Default::default()
247
}
248
}
249
}
250
251
impl From<CircularSector> for Mesh {
252
/// Converts this sector into a [`Mesh`] using a default [`CircularSectorMeshBuilder`].
253
///
254
/// See the documentation of [`CircularSectorMeshBuilder`] for more details.
255
fn from(sector: CircularSector) -> Self {
256
sector.mesh().build()
257
}
258
}
259
260
/// A builder used for creating a [`Mesh`] with a [`CircularSegment`] shape.
261
///
262
/// The resulting mesh will have a UV-map such that the center of the circle is
263
/// at the center of the texture.
264
#[derive(Clone, Copy, Debug, Reflect)]
265
#[reflect(Default, Debug, Clone)]
266
pub struct CircularSegmentMeshBuilder {
267
/// The segment shape.
268
pub segment: CircularSegment,
269
/// The number of vertices used for the arc portion of the segment mesh.
270
/// The default is `32`.
271
#[doc(alias = "vertices")]
272
pub resolution: u32,
273
/// The UV mapping mode
274
pub uv_mode: CircularMeshUvMode,
275
}
276
277
impl Default for CircularSegmentMeshBuilder {
278
fn default() -> Self {
279
Self {
280
segment: CircularSegment::default(),
281
resolution: 32,
282
uv_mode: CircularMeshUvMode::default(),
283
}
284
}
285
}
286
287
impl CircularSegmentMeshBuilder {
288
/// Creates a new [`CircularSegmentMeshBuilder`] from a given segment
289
#[inline]
290
pub fn new(segment: CircularSegment) -> Self {
291
Self {
292
segment,
293
..Self::default()
294
}
295
}
296
297
/// Sets the number of vertices used for the segment mesh.
298
#[inline]
299
#[doc(alias = "vertices")]
300
pub const fn resolution(mut self, resolution: u32) -> Self {
301
self.resolution = resolution;
302
self
303
}
304
305
/// Sets the uv mode used for the segment mesh
306
#[inline]
307
pub const fn uv_mode(mut self, uv_mode: CircularMeshUvMode) -> Self {
308
self.uv_mode = uv_mode;
309
self
310
}
311
}
312
313
impl MeshBuilder for CircularSegmentMeshBuilder {
314
fn build(&self) -> Mesh {
315
let resolution = self.resolution as usize;
316
let mut indices = Vec::with_capacity((resolution - 1) * 3);
317
let mut positions = Vec::with_capacity(resolution + 1);
318
let normals = vec![[0.0, 0.0, 1.0]; resolution + 1];
319
let mut uvs = Vec::with_capacity(resolution + 1);
320
321
let CircularMeshUvMode::Mask { angle: uv_angle } = self.uv_mode;
322
323
// Push the center of the chord.
324
let midpoint_vertex = self.segment.chord_midpoint();
325
positions.push([midpoint_vertex.x, midpoint_vertex.y, 0.0]);
326
// Compute the UV coordinate of the midpoint vertex.
327
// This is similar to the computation inside the loop for the arc vertices,
328
// but the vertex angle is PI/2, and we must scale by the ratio of the apothem to the radius
329
// to correctly position the vertex.
330
let midpoint_uv = Vec2::from_angle(-uv_angle - FRAC_PI_2).mul_add(
331
Vec2::splat(0.5 * (self.segment.apothem() / self.segment.radius())),
332
Vec2::splat(0.5),
333
);
334
uvs.push([midpoint_uv.x, midpoint_uv.y]);
335
336
let first_angle = FRAC_PI_2 - self.segment.half_angle();
337
let last_angle = FRAC_PI_2 + self.segment.half_angle();
338
let last_i = (self.resolution - 1) as f32;
339
for i in 0..self.resolution {
340
let angle = f32::lerp(first_angle, last_angle, i as f32 / last_i);
341
342
// Compute the vertex
343
let vertex = self.segment.radius() * Vec2::from_angle(angle);
344
// Compute the UV coordinate by taking the modified angle's unit vector, negating the Y axis, and rescaling and centering it at (0.5, 0.5).
345
// We accomplish the Y axis flip by negating the angle.
346
let uv =
347
Vec2::from_angle(-(angle + uv_angle)).mul_add(Vec2::splat(0.5), Vec2::splat(0.5));
348
349
positions.push([vertex.x, vertex.y, 0.0]);
350
uvs.push([uv.x, uv.y]);
351
}
352
353
for i in 1..self.resolution {
354
// Index 0 is the midpoint of the chord.
355
indices.extend_from_slice(&[0, i, i + 1]);
356
}
357
358
Mesh::new(
359
PrimitiveTopology::TriangleList,
360
RenderAssetUsages::default(),
361
)
362
.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)
363
.with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)
364
.with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)
365
.with_inserted_indices(Indices::U32(indices))
366
}
367
}
368
369
impl Extrudable for CircularSegmentMeshBuilder {
370
fn perimeter(&self) -> Vec<PerimeterSegment> {
371
let (sin, cos) = ops::sin_cos(self.segment.arc.half_angle);
372
let first_normal = Vec2::new(sin, cos);
373
let last_normal = Vec2::new(-sin, cos);
374
vec![
375
PerimeterSegment::Flat {
376
indices: vec![self.resolution, 0, 1],
377
},
378
PerimeterSegment::Smooth {
379
first_normal,
380
last_normal,
381
indices: (1..=self.resolution).collect(),
382
},
383
]
384
}
385
}
386
387
impl Meshable for CircularSegment {
388
type Output = CircularSegmentMeshBuilder;
389
390
fn mesh(&self) -> Self::Output {
391
CircularSegmentMeshBuilder {
392
segment: *self,
393
..Default::default()
394
}
395
}
396
}
397
398
impl From<CircularSegment> for Mesh {
399
/// Converts this sector into a [`Mesh`] using a default [`CircularSegmentMeshBuilder`].
400
///
401
/// See the documentation of [`CircularSegmentMeshBuilder`] for more details.
402
fn from(segment: CircularSegment) -> Self {
403
segment.mesh().build()
404
}
405
}
406
407
/// A builder used for creating a [`Mesh`] with a [`ConvexPolygon`] shape.
408
///
409
/// You must verify that the `vertices` are not concave when constructing this type. You can
410
/// guarantee this by creating a [`ConvexPolygon`] first, then calling [`ConvexPolygon::mesh()`].
411
#[derive(Clone, Debug, Reflect)]
412
#[reflect(Debug, Clone)]
413
pub struct ConvexPolygonMeshBuilder {
414
pub vertices: Vec<Vec2>,
415
}
416
417
impl Meshable for ConvexPolygon {
418
type Output = ConvexPolygonMeshBuilder;
419
420
fn mesh(&self) -> Self::Output {
421
Self::Output {
422
vertices: self.vertices().to_vec(),
423
}
424
}
425
}
426
427
impl MeshBuilder for ConvexPolygonMeshBuilder {
428
fn build(&self) -> Mesh {
429
let len = self.vertices.len();
430
let mut indices = Vec::with_capacity((len - 2) * 3);
431
let mut positions = Vec::with_capacity(len);
432
433
for vertex in &self.vertices {
434
positions.push([vertex.x, vertex.y, 0.0]);
435
}
436
for i in 2..len as u32 {
437
indices.extend_from_slice(&[0, i - 1, i]);
438
}
439
Mesh::new(
440
PrimitiveTopology::TriangleList,
441
RenderAssetUsages::default(),
442
)
443
.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)
444
.with_inserted_indices(Indices::U32(indices))
445
}
446
}
447
448
impl Extrudable for ConvexPolygonMeshBuilder {
449
fn perimeter(&self) -> Vec<PerimeterSegment> {
450
vec![PerimeterSegment::Flat {
451
indices: (0..self.vertices.len() as u32).chain([0]).collect(),
452
}]
453
}
454
}
455
456
impl From<ConvexPolygon> for Mesh {
457
fn from(polygon: ConvexPolygon) -> Self {
458
polygon.mesh().build()
459
}
460
}
461
462
/// A builder used for creating a [`Mesh`] with a [`RegularPolygon`] shape.
463
#[derive(Clone, Copy, Debug, Reflect)]
464
#[reflect(Default, Debug, Clone)]
465
pub struct RegularPolygonMeshBuilder {
466
circumradius: f32,
467
sides: u32,
468
}
469
470
impl Default for RegularPolygonMeshBuilder {
471
/// Returns the default [`RegularPolygonMeshBuilder`] with six sides (a hexagon) and a circumradius of `0.5`.
472
fn default() -> Self {
473
Self {
474
circumradius: 0.5,
475
sides: 6,
476
}
477
}
478
}
479
480
impl RegularPolygonMeshBuilder {
481
/// Creates a new [`RegularPolygonMeshBuilder`] from the radius of a circumcircle and a number
482
/// of sides.
483
///
484
/// # Panics
485
///
486
/// Panics in debug mode if `circumradius` is negative, or if `sides` is less than 3.
487
pub const fn new(circumradius: f32, sides: u32) -> Self {
488
debug_assert!(
489
circumradius.is_sign_positive(),
490
"polygon has a negative radius"
491
);
492
debug_assert!(sides > 2, "polygon has less than 3 sides");
493
494
Self {
495
circumradius,
496
sides,
497
}
498
}
499
}
500
501
impl Meshable for RegularPolygon {
502
type Output = RegularPolygonMeshBuilder;
503
504
fn mesh(&self) -> Self::Output {
505
Self::Output {
506
circumradius: self.circumcircle.radius,
507
sides: self.sides,
508
}
509
}
510
}
511
512
impl MeshBuilder for RegularPolygonMeshBuilder {
513
fn build(&self) -> Mesh {
514
// The ellipse mesh is just a regular polygon with two radii
515
Ellipse::new(self.circumradius, self.circumradius)
516
.mesh()
517
.resolution(self.sides)
518
.build()
519
}
520
}
521
522
impl Extrudable for RegularPolygonMeshBuilder {
523
fn perimeter(&self) -> Vec<PerimeterSegment> {
524
vec![PerimeterSegment::Flat {
525
indices: (0..self.sides).chain([0]).collect(),
526
}]
527
}
528
}
529
530
impl From<RegularPolygon> for Mesh {
531
fn from(polygon: RegularPolygon) -> Self {
532
polygon.mesh().build()
533
}
534
}
535
536
/// A builder used for creating a [`Mesh`] with an [`Ellipse`] shape.
537
#[derive(Clone, Copy, Debug, Reflect)]
538
#[reflect(Default, Debug, Clone)]
539
pub struct EllipseMeshBuilder {
540
/// The [`Ellipse`] shape.
541
pub ellipse: Ellipse,
542
/// The number of vertices used for the ellipse mesh.
543
/// The default is `32`.
544
#[doc(alias = "vertices")]
545
pub resolution: u32,
546
}
547
548
impl Default for EllipseMeshBuilder {
549
fn default() -> Self {
550
Self {
551
ellipse: Ellipse::default(),
552
resolution: 32,
553
}
554
}
555
}
556
557
impl EllipseMeshBuilder {
558
/// Creates a new [`EllipseMeshBuilder`] from a given half width and half height and a vertex count.
559
#[inline]
560
pub const fn new(half_width: f32, half_height: f32, resolution: u32) -> Self {
561
Self {
562
ellipse: Ellipse::new(half_width, half_height),
563
resolution,
564
}
565
}
566
567
/// Sets the number of vertices used for the ellipse mesh.
568
#[inline]
569
#[doc(alias = "vertices")]
570
pub const fn resolution(mut self, resolution: u32) -> Self {
571
self.resolution = resolution;
572
self
573
}
574
}
575
576
impl MeshBuilder for EllipseMeshBuilder {
577
fn build(&self) -> Mesh {
578
let resolution = self.resolution as usize;
579
let mut indices = Vec::with_capacity((resolution - 2) * 3);
580
let mut positions = Vec::with_capacity(resolution);
581
let normals = vec![[0.0, 0.0, 1.0]; resolution];
582
let mut uvs = Vec::with_capacity(resolution);
583
584
// Add pi/2 so that there is a vertex at the top (sin is 1.0 and cos is 0.0)
585
let start_angle = FRAC_PI_2;
586
let step = core::f32::consts::TAU / self.resolution as f32;
587
588
for i in 0..self.resolution {
589
// Compute vertex position at angle theta
590
let theta = start_angle + i as f32 * step;
591
let (sin, cos) = ops::sin_cos(theta);
592
let x = cos * self.ellipse.half_size.x;
593
let y = sin * self.ellipse.half_size.y;
594
595
positions.push([x, y, 0.0]);
596
uvs.push([0.5 * (cos + 1.0), 1.0 - 0.5 * (sin + 1.0)]);
597
}
598
599
for i in 1..(self.resolution - 1) {
600
indices.extend_from_slice(&[0, i, i + 1]);
601
}
602
603
Mesh::new(
604
PrimitiveTopology::TriangleList,
605
RenderAssetUsages::default(),
606
)
607
.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)
608
.with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)
609
.with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)
610
.with_inserted_indices(Indices::U32(indices))
611
}
612
}
613
614
impl Extrudable for EllipseMeshBuilder {
615
fn perimeter(&self) -> Vec<PerimeterSegment> {
616
vec![PerimeterSegment::Smooth {
617
first_normal: Vec2::Y,
618
last_normal: Vec2::Y,
619
indices: (0..self.resolution).chain([0]).collect(),
620
}]
621
}
622
}
623
624
impl Meshable for Ellipse {
625
type Output = EllipseMeshBuilder;
626
627
fn mesh(&self) -> Self::Output {
628
EllipseMeshBuilder {
629
ellipse: *self,
630
..Default::default()
631
}
632
}
633
}
634
635
impl From<Ellipse> for Mesh {
636
fn from(ellipse: Ellipse) -> Self {
637
ellipse.mesh().build()
638
}
639
}
640
641
/// A builder used for creating a [`Mesh`] with a [`Segment2d`].
642
pub struct Segment2dMeshBuilder {
643
/// The [`Segment2d`] shape.
644
pub segment: Segment2d,
645
}
646
647
impl Segment2dMeshBuilder {
648
/// Creates a new [`Segment2dMeshBuilder`] from a given segment.
649
#[inline]
650
pub const fn new(line: Segment2d) -> Self {
651
Self { segment: line }
652
}
653
}
654
655
impl MeshBuilder for Segment2dMeshBuilder {
656
fn build(&self) -> Mesh {
657
let positions = self.segment.vertices.map(|v| v.extend(0.0)).to_vec();
658
let indices = Indices::U32(vec![0, 1]);
659
660
Mesh::new(PrimitiveTopology::LineList, RenderAssetUsages::default())
661
.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)
662
.with_inserted_indices(indices)
663
}
664
}
665
666
impl Meshable for Segment2d {
667
type Output = Segment2dMeshBuilder;
668
669
fn mesh(&self) -> Self::Output {
670
Segment2dMeshBuilder::new(*self)
671
}
672
}
673
674
impl From<Segment2d> for Mesh {
675
/// Converts this segment into a [`Mesh`] using a default [`Segment2dMeshBuilder`].
676
fn from(segment: Segment2d) -> Self {
677
segment.mesh().build()
678
}
679
}
680
681
/// A builder used for creating a [`Mesh`] with a [`Polyline2d`] shape.
682
#[derive(Clone, Debug, Default, Reflect)]
683
#[reflect(Default, Debug, Clone)]
684
pub struct Polyline2dMeshBuilder {
685
polyline: Polyline2d,
686
}
687
688
impl MeshBuilder for Polyline2dMeshBuilder {
689
fn build(&self) -> Mesh {
690
let positions: Vec<_> = self
691
.polyline
692
.vertices
693
.iter()
694
.map(|v| v.extend(0.0))
695
.collect();
696
697
let indices = Indices::U32(
698
(0..self.polyline.vertices.len() as u32 - 1)
699
.flat_map(|i| [i, i + 1])
700
.collect(),
701
);
702
703
Mesh::new(PrimitiveTopology::LineList, RenderAssetUsages::default())
704
.with_inserted_indices(indices)
705
.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)
706
}
707
}
708
709
impl Meshable for Polyline2d {
710
type Output = Polyline2dMeshBuilder;
711
712
fn mesh(&self) -> Self::Output {
713
Polyline2dMeshBuilder {
714
polyline: self.clone(),
715
}
716
}
717
}
718
719
impl From<Polyline2d> for Mesh {
720
fn from(polyline: Polyline2d) -> Self {
721
polyline.mesh().build()
722
}
723
}
724
725
/// A builder for creating a [`Mesh`] with an [`Annulus`] shape.
726
#[derive(Clone, Copy, Debug, Reflect)]
727
#[reflect(Default, Debug, Clone)]
728
pub struct AnnulusMeshBuilder {
729
/// The [`Annulus`] shape.
730
pub annulus: Annulus,
731
732
/// The number of vertices used in constructing each concentric circle of the annulus mesh.
733
/// The default is `32`.
734
pub resolution: u32,
735
}
736
737
impl Default for AnnulusMeshBuilder {
738
fn default() -> Self {
739
Self {
740
annulus: Annulus::default(),
741
resolution: 32,
742
}
743
}
744
}
745
746
impl AnnulusMeshBuilder {
747
/// Create an [`AnnulusMeshBuilder`] with the given inner radius, outer radius, and angular vertex count.
748
#[inline]
749
pub fn new(inner_radius: f32, outer_radius: f32, resolution: u32) -> Self {
750
Self {
751
annulus: Annulus::new(inner_radius, outer_radius),
752
resolution,
753
}
754
}
755
756
/// Sets the number of vertices used in constructing the concentric circles of the annulus mesh.
757
#[inline]
758
pub fn resolution(mut self, resolution: u32) -> Self {
759
self.resolution = resolution;
760
self
761
}
762
}
763
764
impl MeshBuilder for AnnulusMeshBuilder {
765
fn build(&self) -> Mesh {
766
let inner_radius = self.annulus.inner_circle.radius;
767
let outer_radius = self.annulus.outer_circle.radius;
768
769
let num_vertices = (self.resolution as usize + 1) * 2;
770
let mut indices = Vec::with_capacity(self.resolution as usize * 6);
771
let mut positions = Vec::with_capacity(num_vertices);
772
let mut uvs = Vec::with_capacity(num_vertices);
773
let normals = vec![[0.0, 0.0, 1.0]; num_vertices];
774
775
// We have one more set of vertices than might be naïvely expected;
776
// the vertices at `start_angle` are duplicated for the purposes of UV
777
// mapping. Here, each iteration places a pair of vertices at a fixed
778
// angle from the center of the annulus.
779
let start_angle = FRAC_PI_2;
780
let step = core::f32::consts::TAU / self.resolution as f32;
781
for i in 0..=self.resolution {
782
let theta = start_angle + (i % self.resolution) as f32 * step;
783
let (sin, cos) = ops::sin_cos(theta);
784
let inner_pos = [cos * inner_radius, sin * inner_radius, 0.];
785
let outer_pos = [cos * outer_radius, sin * outer_radius, 0.];
786
positions.push(inner_pos);
787
positions.push(outer_pos);
788
789
// The first UV direction is radial and the second is angular;
790
// i.e., a single UV rectangle is stretched around the annulus, with
791
// its top and bottom meeting as the circle closes. Lines of constant
792
// U map to circles, and lines of constant V map to radial line segments.
793
let inner_uv = [0., i as f32 / self.resolution as f32];
794
let outer_uv = [1., i as f32 / self.resolution as f32];
795
uvs.push(inner_uv);
796
uvs.push(outer_uv);
797
}
798
799
// Adjacent pairs of vertices form two triangles with each other; here,
800
// we are just making sure that they both have the right orientation,
801
// which is the CCW order of
802
// `inner_vertex` -> `outer_vertex` -> `next_outer` -> `next_inner`
803
for i in 0..self.resolution {
804
let inner_vertex = 2 * i;
805
let outer_vertex = 2 * i + 1;
806
let next_inner = inner_vertex + 2;
807
let next_outer = outer_vertex + 2;
808
indices.extend_from_slice(&[inner_vertex, outer_vertex, next_outer]);
809
indices.extend_from_slice(&[next_outer, next_inner, inner_vertex]);
810
}
811
812
Mesh::new(
813
PrimitiveTopology::TriangleList,
814
RenderAssetUsages::default(),
815
)
816
.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)
817
.with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)
818
.with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)
819
.with_inserted_indices(Indices::U32(indices))
820
}
821
}
822
823
impl Extrudable for AnnulusMeshBuilder {
824
fn perimeter(&self) -> Vec<PerimeterSegment> {
825
let vert_count = 2 * self.resolution;
826
vec![
827
PerimeterSegment::Smooth {
828
first_normal: Vec2::NEG_Y,
829
last_normal: Vec2::NEG_Y,
830
indices: (0..vert_count).step_by(2).chain([0]).rev().collect(), // Inner hole
831
},
832
PerimeterSegment::Smooth {
833
first_normal: Vec2::Y,
834
last_normal: Vec2::Y,
835
indices: (1..vert_count).step_by(2).chain([1]).collect(), // Outer perimeter
836
},
837
]
838
}
839
}
840
841
impl Meshable for Annulus {
842
type Output = AnnulusMeshBuilder;
843
844
fn mesh(&self) -> Self::Output {
845
AnnulusMeshBuilder {
846
annulus: *self,
847
..Default::default()
848
}
849
}
850
}
851
852
impl From<Annulus> for Mesh {
853
fn from(annulus: Annulus) -> Self {
854
annulus.mesh().build()
855
}
856
}
857
858
/// A builder for creating a [`Mesh`] with an [`Rhombus`] shape.
859
#[derive(Clone, Copy, Debug, Reflect)]
860
#[reflect(Default, Debug, Clone)]
861
pub struct RhombusMeshBuilder {
862
half_diagonals: Vec2,
863
}
864
865
impl Default for RhombusMeshBuilder {
866
/// Returns the default [`RhombusMeshBuilder`] with a half-horizontal and half-vertical diagonal of `0.5`.
867
fn default() -> Self {
868
Self {
869
half_diagonals: Vec2::splat(0.5),
870
}
871
}
872
}
873
874
impl RhombusMeshBuilder {
875
/// Creates a new [`RhombusMeshBuilder`] from a horizontal and vertical diagonal size.
876
///
877
/// # Panics
878
///
879
/// Panics in debug mode if `horizontal_diagonal` or `vertical_diagonal` is negative.
880
pub const fn new(horizontal_diagonal: f32, vertical_diagonal: f32) -> Self {
881
debug_assert!(
882
horizontal_diagonal >= 0.0,
883
"rhombus has a negative horizontal size",
884
);
885
debug_assert!(
886
vertical_diagonal >= 0.0,
887
"rhombus has a negative vertical size"
888
);
889
890
Self {
891
half_diagonals: Vec2::new(horizontal_diagonal / 2.0, vertical_diagonal / 2.0),
892
}
893
}
894
}
895
896
impl MeshBuilder for RhombusMeshBuilder {
897
fn build(&self) -> Mesh {
898
let [hhd, vhd] = [self.half_diagonals.x, self.half_diagonals.y];
899
let positions = vec![
900
[hhd, 0.0, 0.0],
901
[-hhd, 0.0, 0.0],
902
[0.0, vhd, 0.0],
903
[0.0, -vhd, 0.0],
904
];
905
let normals = vec![[0.0, 0.0, 1.0]; 4];
906
let uvs = vec![[1.0, 0.5], [0.0, 0.5], [0.5, 0.0], [0.5, 1.0]];
907
let indices = Indices::U32(vec![1, 0, 2, 1, 3, 0]);
908
909
Mesh::new(
910
PrimitiveTopology::TriangleList,
911
RenderAssetUsages::default(),
912
)
913
.with_inserted_indices(indices)
914
.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)
915
.with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)
916
.with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)
917
}
918
}
919
920
impl Extrudable for RhombusMeshBuilder {
921
fn perimeter(&self) -> Vec<PerimeterSegment> {
922
vec![PerimeterSegment::Flat {
923
indices: vec![0, 2, 1, 3, 0],
924
}]
925
}
926
}
927
928
impl Meshable for Rhombus {
929
type Output = RhombusMeshBuilder;
930
931
fn mesh(&self) -> Self::Output {
932
Self::Output {
933
half_diagonals: self.half_diagonals,
934
}
935
}
936
}
937
938
impl From<Rhombus> for Mesh {
939
fn from(rhombus: Rhombus) -> Self {
940
rhombus.mesh().build()
941
}
942
}
943
944
/// A builder used for creating a [`Mesh`] with a [`Triangle2d`] shape.
945
#[derive(Clone, Copy, Debug, Default, Reflect)]
946
#[reflect(Default, Debug, Clone)]
947
pub struct Triangle2dMeshBuilder {
948
triangle: Triangle2d,
949
}
950
951
impl Triangle2dMeshBuilder {
952
/// Creates a new [`Triangle2dMeshBuilder`] from the points `a`, `b`, and `c`.
953
pub const fn new(a: Vec2, b: Vec2, c: Vec2) -> Self {
954
Self {
955
triangle: Triangle2d::new(a, b, c),
956
}
957
}
958
}
959
960
impl Meshable for Triangle2d {
961
type Output = Triangle2dMeshBuilder;
962
963
fn mesh(&self) -> Self::Output {
964
Self::Output { triangle: *self }
965
}
966
}
967
968
impl MeshBuilder for Triangle2dMeshBuilder {
969
fn build(&self) -> Mesh {
970
let vertices_3d = self.triangle.vertices.map(|v| v.extend(0.));
971
972
let positions: Vec<_> = vertices_3d.into();
973
let normals = vec![[0.0, 0.0, 1.0]; 3];
974
975
let uvs: Vec<_> = triangle3d::uv_coords(&Triangle3d::new(
976
vertices_3d[0],
977
vertices_3d[1],
978
vertices_3d[2],
979
))
980
.into();
981
982
let is_ccw = self.triangle.winding_order() == WindingOrder::CounterClockwise;
983
let indices = if is_ccw {
984
Indices::U32(vec![0, 1, 2])
985
} else {
986
Indices::U32(vec![2, 1, 0])
987
};
988
989
Mesh::new(
990
PrimitiveTopology::TriangleList,
991
RenderAssetUsages::default(),
992
)
993
.with_inserted_indices(indices)
994
.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)
995
.with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)
996
.with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)
997
}
998
}
999
1000
impl Extrudable for Triangle2dMeshBuilder {
1001
fn perimeter(&self) -> Vec<PerimeterSegment> {
1002
let is_ccw = self.triangle.winding_order() == WindingOrder::CounterClockwise;
1003
if is_ccw {
1004
vec![PerimeterSegment::Flat {
1005
indices: vec![0, 1, 2, 0],
1006
}]
1007
} else {
1008
vec![PerimeterSegment::Flat {
1009
indices: vec![2, 1, 0, 2],
1010
}]
1011
}
1012
}
1013
}
1014
1015
impl From<Triangle2d> for Mesh {
1016
fn from(triangle: Triangle2d) -> Self {
1017
triangle.mesh().build()
1018
}
1019
}
1020
1021
/// A builder used for creating a [`Mesh`] with a [`Rectangle`] shape.
1022
#[derive(Clone, Copy, Debug, Reflect)]
1023
#[reflect(Default, Debug, Clone)]
1024
pub struct RectangleMeshBuilder {
1025
half_size: Vec2,
1026
}
1027
1028
impl Default for RectangleMeshBuilder {
1029
/// Returns the default [`RectangleMeshBuilder`] with a half-width and half-height of `0.5`.
1030
fn default() -> Self {
1031
Self {
1032
half_size: Vec2::splat(0.5),
1033
}
1034
}
1035
}
1036
1037
impl RectangleMeshBuilder {
1038
/// Creates a new [`RectangleMeshBuilder`] from a full width and height.
1039
///
1040
/// # Panics
1041
///
1042
/// Panics in debug mode if `width` or `height` is negative.
1043
pub const fn new(width: f32, height: f32) -> Self {
1044
debug_assert!(width >= 0.0, "rectangle has a negative width");
1045
debug_assert!(height >= 0.0, "rectangle has a negative height");
1046
1047
Self {
1048
half_size: Vec2::new(width / 2.0, height / 2.0),
1049
}
1050
}
1051
}
1052
1053
impl MeshBuilder for RectangleMeshBuilder {
1054
fn build(&self) -> Mesh {
1055
let [hw, hh] = [self.half_size.x, self.half_size.y];
1056
let positions = vec![
1057
[hw, hh, 0.0],
1058
[-hw, hh, 0.0],
1059
[-hw, -hh, 0.0],
1060
[hw, -hh, 0.0],
1061
];
1062
let normals = vec![[0.0, 0.0, 1.0]; 4];
1063
let uvs = vec![[1.0, 0.0], [0.0, 0.0], [0.0, 1.0], [1.0, 1.0]];
1064
let indices = Indices::U32(vec![0, 1, 2, 0, 2, 3]);
1065
1066
Mesh::new(
1067
PrimitiveTopology::TriangleList,
1068
RenderAssetUsages::default(),
1069
)
1070
.with_inserted_indices(indices)
1071
.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)
1072
.with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)
1073
.with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)
1074
}
1075
}
1076
1077
impl Extrudable for RectangleMeshBuilder {
1078
fn perimeter(&self) -> Vec<PerimeterSegment> {
1079
vec![PerimeterSegment::Flat {
1080
indices: vec![0, 1, 2, 3, 0],
1081
}]
1082
}
1083
}
1084
1085
impl Meshable for Rectangle {
1086
type Output = RectangleMeshBuilder;
1087
1088
fn mesh(&self) -> Self::Output {
1089
RectangleMeshBuilder {
1090
half_size: self.half_size,
1091
}
1092
}
1093
}
1094
1095
impl From<Rectangle> for Mesh {
1096
fn from(rectangle: Rectangle) -> Self {
1097
rectangle.mesh().build()
1098
}
1099
}
1100
1101
/// A builder used for creating a [`Mesh`] with a [`Capsule2d`] shape.
1102
#[derive(Clone, Copy, Debug, Reflect)]
1103
#[reflect(Default, Debug, Clone)]
1104
pub struct Capsule2dMeshBuilder {
1105
/// The [`Capsule2d`] shape.
1106
pub capsule: Capsule2d,
1107
/// The number of vertices used for one hemicircle.
1108
/// The total number of vertices for the capsule mesh will be two times the resolution.
1109
///
1110
/// The default is `16`.
1111
pub resolution: u32,
1112
}
1113
1114
impl Default for Capsule2dMeshBuilder {
1115
fn default() -> Self {
1116
Self {
1117
capsule: Capsule2d::default(),
1118
resolution: 16,
1119
}
1120
}
1121
}
1122
1123
impl Capsule2dMeshBuilder {
1124
/// Creates a new [`Capsule2dMeshBuilder`] from a given radius, length, and the number of vertices
1125
/// used for one hemicircle. The total number of vertices for the capsule mesh will be two times the resolution.
1126
#[inline]
1127
pub fn new(radius: f32, length: f32, resolution: u32) -> Self {
1128
Self {
1129
capsule: Capsule2d::new(radius, length),
1130
resolution,
1131
}
1132
}
1133
1134
/// Sets the number of vertices used for one hemicircle.
1135
/// The total number of vertices for the capsule mesh will be two times the resolution.
1136
#[inline]
1137
pub const fn resolution(mut self, resolution: u32) -> Self {
1138
self.resolution = resolution;
1139
self
1140
}
1141
}
1142
1143
impl MeshBuilder for Capsule2dMeshBuilder {
1144
fn build(&self) -> Mesh {
1145
// The resolution is the number of vertices for one semicircle
1146
let resolution = self.resolution;
1147
let vertex_count = 2 * resolution;
1148
1149
// Six extra indices for the two triangles between the semicircles
1150
let mut indices = Vec::with_capacity((resolution as usize - 2) * 2 * 3 + 6);
1151
let mut positions = Vec::with_capacity(vertex_count as usize);
1152
let normals = vec![[0.0, 0.0, 1.0]; vertex_count as usize];
1153
let mut uvs = Vec::with_capacity(vertex_count as usize);
1154
1155
let radius = self.capsule.radius;
1156
let step = core::f32::consts::TAU / vertex_count as f32;
1157
1158
// If the vertex count is even, offset starting angle of top semicircle by half a step
1159
// to position the vertices evenly.
1160
let start_angle = if vertex_count.is_multiple_of(2) {
1161
step / 2.0
1162
} else {
1163
0.0
1164
};
1165
1166
// How much the hemicircle radius is of the total half-height of the capsule.
1167
// This is used to prevent the UVs from stretching between the semicircles.
1168
let radius_frac = self.capsule.radius / (self.capsule.half_length + self.capsule.radius);
1169
1170
// Create top semicircle
1171
for i in 0..resolution {
1172
// Compute vertex position at angle theta
1173
let theta = start_angle + i as f32 * step;
1174
let (sin, cos) = ops::sin_cos(theta);
1175
let (x, y) = (cos * radius, sin * radius + self.capsule.half_length);
1176
1177
positions.push([x, y, 0.0]);
1178
uvs.push([0.5 * (cos + 1.0), radius_frac * (1.0 - 0.5 * (sin + 1.0))]);
1179
}
1180
1181
// Add top semicircle indices
1182
for i in 1..resolution - 1 {
1183
indices.extend_from_slice(&[0, i, i + 1]);
1184
}
1185
1186
// Add indices for top left triangle of the part between the semicircles
1187
indices.extend_from_slice(&[0, resolution - 1, resolution]);
1188
1189
// Create bottom semicircle
1190
for i in resolution..vertex_count {
1191
// Compute vertex position at angle theta
1192
let theta = start_angle + i as f32 * step;
1193
let (sin, cos) = ops::sin_cos(theta);
1194
let (x, y) = (cos * radius, sin * radius - self.capsule.half_length);
1195
1196
positions.push([x, y, 0.0]);
1197
uvs.push([0.5 * (cos + 1.0), 1.0 - radius_frac * 0.5 * (sin + 1.0)]);
1198
}
1199
1200
// Add bottom semicircle indices
1201
for i in 1..resolution - 1 {
1202
indices.extend_from_slice(&[resolution, resolution + i, resolution + i + 1]);
1203
}
1204
1205
// Add indices for bottom right triangle of the part between the semicircles
1206
indices.extend_from_slice(&[resolution, vertex_count - 1, 0]);
1207
1208
Mesh::new(
1209
PrimitiveTopology::TriangleList,
1210
RenderAssetUsages::default(),
1211
)
1212
.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)
1213
.with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)
1214
.with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)
1215
.with_inserted_indices(Indices::U32(indices))
1216
}
1217
}
1218
1219
impl Extrudable for Capsule2dMeshBuilder {
1220
fn perimeter(&self) -> Vec<PerimeterSegment> {
1221
let resolution = self.resolution;
1222
let top_semi_indices = (0..resolution).collect();
1223
let bottom_semi_indices = (resolution..(2 * resolution)).collect();
1224
vec![
1225
PerimeterSegment::Smooth {
1226
first_normal: Vec2::X,
1227
last_normal: Vec2::NEG_X,
1228
indices: top_semi_indices,
1229
}, // Top semi-circle
1230
PerimeterSegment::Flat {
1231
indices: vec![resolution - 1, resolution],
1232
}, // Left edge
1233
PerimeterSegment::Smooth {
1234
first_normal: Vec2::NEG_X,
1235
last_normal: Vec2::X,
1236
indices: bottom_semi_indices,
1237
}, // Bottom semi-circle
1238
PerimeterSegment::Flat {
1239
indices: vec![2 * resolution - 1, 0],
1240
}, // Right edge
1241
]
1242
}
1243
}
1244
1245
impl Meshable for Capsule2d {
1246
type Output = Capsule2dMeshBuilder;
1247
1248
fn mesh(&self) -> Self::Output {
1249
Capsule2dMeshBuilder {
1250
capsule: *self,
1251
..Default::default()
1252
}
1253
}
1254
}
1255
1256
impl From<Capsule2d> for Mesh {
1257
fn from(capsule: Capsule2d) -> Self {
1258
capsule.mesh().build()
1259
}
1260
}
1261
1262
#[cfg(test)]
1263
mod tests {
1264
use bevy_math::{prelude::Annulus, primitives::RegularPolygon, FloatOrd};
1265
use bevy_platform::collections::HashSet;
1266
1267
use crate::{Mesh, MeshBuilder, Meshable, VertexAttributeValues};
1268
1269
fn count_distinct_positions(points: &[[f32; 3]]) -> usize {
1270
let mut map = <HashSet<_>>::default();
1271
for point in points {
1272
map.insert(point.map(FloatOrd));
1273
}
1274
map.len()
1275
}
1276
1277
#[test]
1278
fn test_annulus() {
1279
let mesh = Annulus::new(1.0, 1.2).mesh().resolution(16).build();
1280
1281
assert_eq!(
1282
32,
1283
count_distinct_positions(
1284
mesh.attribute(Mesh::ATTRIBUTE_POSITION)
1285
.unwrap()
1286
.as_float3()
1287
.unwrap()
1288
)
1289
);
1290
}
1291
1292
/// Sin/cos and multiplication computations result in numbers like 0.4999999.
1293
/// Round these to numbers we expect like 0.5.
1294
fn fix_floats<const N: usize>(points: &mut [[f32; N]]) {
1295
for point in points.iter_mut() {
1296
for coord in point.iter_mut() {
1297
let round = (*coord * 2.).round() / 2.;
1298
if (*coord - round).abs() < 0.00001 {
1299
*coord = round;
1300
}
1301
}
1302
}
1303
}
1304
1305
#[test]
1306
fn test_regular_polygon() {
1307
let mut mesh = Mesh::from(RegularPolygon::new(7.0, 4));
1308
1309
let Some(VertexAttributeValues::Float32x3(mut positions)) =
1310
mesh.remove_attribute(Mesh::ATTRIBUTE_POSITION)
1311
else {
1312
panic!("Expected positions f32x3");
1313
};
1314
let Some(VertexAttributeValues::Float32x2(mut uvs)) =
1315
mesh.remove_attribute(Mesh::ATTRIBUTE_UV_0)
1316
else {
1317
panic!("Expected uvs f32x2");
1318
};
1319
let Some(VertexAttributeValues::Float32x3(normals)) =
1320
mesh.remove_attribute(Mesh::ATTRIBUTE_NORMAL)
1321
else {
1322
panic!("Expected normals f32x3");
1323
};
1324
1325
fix_floats(&mut positions);
1326
fix_floats(&mut uvs);
1327
1328
assert_eq!(
1329
[
1330
[0.0, 7.0, 0.0],
1331
[-7.0, 0.0, 0.0],
1332
[0.0, -7.0, 0.0],
1333
[7.0, 0.0, 0.0],
1334
],
1335
&positions[..]
1336
);
1337
1338
// Note V coordinate increases in the opposite direction to the Y coordinate.
1339
assert_eq!([[0.5, 0.0], [0.0, 0.5], [0.5, 1.0], [1.0, 0.5],], &uvs[..]);
1340
1341
assert_eq!(&[[0.0, 0.0, 1.0]; 4], &normals[..]);
1342
}
1343
}
1344
1345