bevy_gizmos/primitives/
dim2.rs

1//! A module for rendering each of the 2D [`bevy_math::primitives`] with [`GizmoBuffer`].
2
3use core::f32::consts::{FRAC_PI_2, PI};
4
5use super::helpers::*;
6
7use bevy_color::Color;
8use bevy_math::{
9    primitives::{
10        Annulus, Arc2d, Capsule2d, Circle, CircularSector, CircularSegment, Ellipse, Line2d,
11        Plane2d, Polygon, Polyline2d, Primitive2d, Rectangle, RegularPolygon, Rhombus, Segment2d,
12        Triangle2d,
13    },
14    Dir2, Isometry2d, Rot2, Vec2,
15};
16
17use crate::{gizmos::GizmoBuffer, prelude::GizmoConfigGroup};
18
19// some magic number since using directions as offsets will result in lines of length 1 pixel
20const MIN_LINE_LEN: f32 = 50.0;
21const HALF_MIN_LINE_LEN: f32 = 25.0;
22// length used to simulate infinite lines
23const INFINITE_LEN: f32 = 100_000.0;
24
25/// A trait for rendering 2D geometric primitives (`P`) with [`GizmoBuffer`].
26pub trait GizmoPrimitive2d<P: Primitive2d> {
27    /// The output of `primitive_2d`. This is a builder to set non-default values.
28    type Output<'a>
29    where
30        Self: 'a;
31
32    /// Renders a 2D primitive with its associated details.
33    fn primitive_2d(
34        &mut self,
35        primitive: &P,
36        isometry: impl Into<Isometry2d>,
37        color: impl Into<Color>,
38    ) -> Self::Output<'_>;
39}
40
41// direction 2d
42
43impl<Config, Clear> GizmoPrimitive2d<Dir2> for GizmoBuffer<Config, Clear>
44where
45    Config: GizmoConfigGroup,
46    Clear: 'static + Send + Sync,
47{
48    type Output<'a>
49        = ()
50    where
51        Self: 'a;
52
53    fn primitive_2d(
54        &mut self,
55        primitive: &Dir2,
56        isometry: impl Into<Isometry2d>,
57        color: impl Into<Color>,
58    ) -> Self::Output<'_> {
59        if !self.enabled {
60            return;
61        }
62        let isometry = isometry.into();
63        let start = Vec2::ZERO;
64        let end = *primitive * MIN_LINE_LEN;
65        self.arrow_2d(isometry * start, isometry * end, color);
66    }
67}
68
69// arc 2d
70
71impl<Config, Clear> GizmoPrimitive2d<Arc2d> for GizmoBuffer<Config, Clear>
72where
73    Config: GizmoConfigGroup,
74    Clear: 'static + Send + Sync,
75{
76    type Output<'a>
77        = ()
78    where
79        Self: 'a;
80
81    fn primitive_2d(
82        &mut self,
83        primitive: &Arc2d,
84        isometry: impl Into<Isometry2d>,
85        color: impl Into<Color>,
86    ) -> Self::Output<'_> {
87        if !self.enabled {
88            return;
89        }
90
91        let isometry = isometry.into();
92        let start_iso = isometry * Isometry2d::from_rotation(Rot2::radians(-primitive.half_angle));
93
94        self.arc_2d(
95            start_iso,
96            primitive.half_angle * 2.0,
97            primitive.radius,
98            color,
99        );
100    }
101}
102
103// circle 2d
104
105impl<Config, Clear> GizmoPrimitive2d<Circle> for GizmoBuffer<Config, Clear>
106where
107    Config: GizmoConfigGroup,
108    Clear: 'static + Send + Sync,
109{
110    type Output<'a>
111        = crate::circles::Ellipse2dBuilder<'a, Config, Clear>
112    where
113        Self: 'a;
114
115    fn primitive_2d(
116        &mut self,
117        primitive: &Circle,
118        isometry: impl Into<Isometry2d>,
119        color: impl Into<Color>,
120    ) -> Self::Output<'_> {
121        self.circle_2d(isometry, primitive.radius, color)
122    }
123}
124
125// circular sector 2d
126
127impl<Config, Clear> GizmoPrimitive2d<CircularSector> for GizmoBuffer<Config, Clear>
128where
129    Config: GizmoConfigGroup,
130    Clear: 'static + Send + Sync,
131{
132    type Output<'a>
133        = ()
134    where
135        Self: 'a;
136
137    fn primitive_2d(
138        &mut self,
139        primitive: &CircularSector,
140        isometry: impl Into<Isometry2d>,
141        color: impl Into<Color>,
142    ) -> Self::Output<'_> {
143        if !self.enabled {
144            return;
145        }
146
147        let isometry = isometry.into();
148        let color = color.into();
149
150        let start_iso =
151            isometry * Isometry2d::from_rotation(Rot2::radians(-primitive.arc.half_angle));
152        let end_iso = isometry * Isometry2d::from_rotation(Rot2::radians(primitive.arc.half_angle));
153
154        // we need to draw the arc part of the sector, and the two lines connecting the arc and the center
155        self.arc_2d(
156            start_iso,
157            primitive.arc.half_angle * 2.0,
158            primitive.arc.radius,
159            color,
160        );
161
162        let end_position = primitive.arc.radius * Vec2::Y;
163        self.line_2d(isometry * Vec2::ZERO, start_iso * end_position, color);
164        self.line_2d(isometry * Vec2::ZERO, end_iso * end_position, color);
165    }
166}
167
168// circular segment 2d
169
170impl<Config, Clear> GizmoPrimitive2d<CircularSegment> for GizmoBuffer<Config, Clear>
171where
172    Config: GizmoConfigGroup,
173    Clear: 'static + Send + Sync,
174{
175    type Output<'a>
176        = ()
177    where
178        Self: 'a;
179
180    fn primitive_2d(
181        &mut self,
182        primitive: &CircularSegment,
183        isometry: impl Into<Isometry2d>,
184        color: impl Into<Color>,
185    ) -> Self::Output<'_> {
186        if !self.enabled {
187            return;
188        }
189
190        let isometry = isometry.into();
191        let color = color.into();
192
193        let start_iso =
194            isometry * Isometry2d::from_rotation(Rot2::radians(-primitive.arc.half_angle));
195        let end_iso = isometry * Isometry2d::from_rotation(Rot2::radians(primitive.arc.half_angle));
196
197        // we need to draw the arc part of the segment, and the line connecting the two ends
198        self.arc_2d(
199            start_iso,
200            primitive.arc.half_angle * 2.0,
201            primitive.arc.radius,
202            color,
203        );
204
205        let position = primitive.arc.radius * Vec2::Y;
206        self.line_2d(start_iso * position, end_iso * position, color);
207    }
208}
209
210// ellipse 2d
211
212impl<Config, Clear> GizmoPrimitive2d<Ellipse> for GizmoBuffer<Config, Clear>
213where
214    Config: GizmoConfigGroup,
215    Clear: 'static + Send + Sync,
216{
217    type Output<'a>
218        = crate::circles::Ellipse2dBuilder<'a, Config, Clear>
219    where
220        Self: 'a;
221
222    fn primitive_2d<'a>(
223        &mut self,
224        primitive: &Ellipse,
225        isometry: impl Into<Isometry2d>,
226        color: impl Into<Color>,
227    ) -> Self::Output<'_> {
228        self.ellipse_2d(isometry, primitive.half_size, color)
229    }
230}
231
232// annulus 2d
233
234/// Builder for configuring the drawing options of [`Annulus`].
235pub struct Annulus2dBuilder<'a, Config, Clear>
236where
237    Config: GizmoConfigGroup,
238    Clear: 'static + Send + Sync,
239{
240    gizmos: &'a mut GizmoBuffer<Config, Clear>,
241    isometry: Isometry2d,
242    inner_radius: f32,
243    outer_radius: f32,
244    color: Color,
245    inner_resolution: u32,
246    outer_resolution: u32,
247}
248
249impl<Config, Clear> Annulus2dBuilder<'_, Config, Clear>
250where
251    Config: GizmoConfigGroup,
252    Clear: 'static + Send + Sync,
253{
254    /// Set the number of line-segments for each circle of the annulus.
255    pub fn resolution(mut self, resolution: u32) -> Self {
256        self.outer_resolution = resolution;
257        self.inner_resolution = resolution;
258        self
259    }
260
261    /// Set the number of line-segments for the outer circle of the annulus.
262    pub fn outer_resolution(mut self, resolution: u32) -> Self {
263        self.outer_resolution = resolution;
264        self
265    }
266
267    /// Set the number of line-segments for the inner circle of the annulus.
268    pub fn inner_resolution(mut self, resolution: u32) -> Self {
269        self.inner_resolution = resolution;
270        self
271    }
272}
273
274impl<Config, Clear> GizmoPrimitive2d<Annulus> for GizmoBuffer<Config, Clear>
275where
276    Config: GizmoConfigGroup,
277    Clear: 'static + Send + Sync,
278{
279    type Output<'a>
280        = Annulus2dBuilder<'a, Config, Clear>
281    where
282        Self: 'a;
283
284    fn primitive_2d(
285        &mut self,
286        primitive: &Annulus,
287        isometry: impl Into<Isometry2d>,
288        color: impl Into<Color>,
289    ) -> Self::Output<'_> {
290        Annulus2dBuilder {
291            gizmos: self,
292            isometry: isometry.into(),
293            inner_radius: primitive.inner_circle.radius,
294            outer_radius: primitive.outer_circle.radius,
295            color: color.into(),
296            inner_resolution: crate::circles::DEFAULT_CIRCLE_RESOLUTION,
297            outer_resolution: crate::circles::DEFAULT_CIRCLE_RESOLUTION,
298        }
299    }
300}
301
302impl<Config, Clear> Drop for Annulus2dBuilder<'_, Config, Clear>
303where
304    Config: GizmoConfigGroup,
305    Clear: 'static + Send + Sync,
306{
307    fn drop(&mut self) {
308        if !self.gizmos.enabled {
309            return;
310        }
311
312        let Annulus2dBuilder {
313            gizmos,
314            isometry,
315            inner_radius,
316            outer_radius,
317            inner_resolution,
318            outer_resolution,
319            color,
320            ..
321        } = self;
322
323        gizmos
324            .circle_2d(*isometry, *outer_radius, *color)
325            .resolution(*outer_resolution);
326        gizmos
327            .circle_2d(*isometry, *inner_radius, *color)
328            .resolution(*inner_resolution);
329    }
330}
331
332// rhombus 2d
333
334impl<Config, Clear> GizmoPrimitive2d<Rhombus> for GizmoBuffer<Config, Clear>
335where
336    Config: GizmoConfigGroup,
337    Clear: 'static + Send + Sync,
338{
339    type Output<'a>
340        = ()
341    where
342        Self: 'a;
343
344    fn primitive_2d(
345        &mut self,
346        primitive: &Rhombus,
347        isometry: impl Into<Isometry2d>,
348        color: impl Into<Color>,
349    ) -> Self::Output<'_> {
350        if !self.enabled {
351            return;
352        };
353        let isometry = isometry.into();
354        let [a, b, c, d] =
355            [(1.0, 0.0), (0.0, 1.0), (-1.0, 0.0), (0.0, -1.0)].map(|(sign_x, sign_y)| {
356                Vec2::new(
357                    primitive.half_diagonals.x * sign_x,
358                    primitive.half_diagonals.y * sign_y,
359                )
360            });
361        let positions = [a, b, c, d, a].map(|vec2| isometry * vec2);
362        self.linestrip_2d(positions, color);
363    }
364}
365
366// capsule 2d
367
368impl<Config, Clear> GizmoPrimitive2d<Capsule2d> for GizmoBuffer<Config, Clear>
369where
370    Config: GizmoConfigGroup,
371    Clear: 'static + Send + Sync,
372{
373    type Output<'a>
374        = ()
375    where
376        Self: 'a;
377
378    fn primitive_2d(
379        &mut self,
380        primitive: &Capsule2d,
381        isometry: impl Into<Isometry2d>,
382        color: impl Into<Color>,
383    ) -> Self::Output<'_> {
384        let isometry = isometry.into();
385        let polymorphic_color: Color = color.into();
386
387        if !self.enabled {
388            return;
389        }
390
391        // transform points from the reference unit square to capsule "rectangle"
392        let [top_left, top_right, bottom_left, bottom_right, top_center, bottom_center] = [
393            [-1.0, 1.0],
394            [1.0, 1.0],
395            [-1.0, -1.0],
396            [1.0, -1.0],
397            // just reuse the pipeline for these points as well
398            [0.0, 1.0],
399            [0.0, -1.0],
400        ]
401        .map(|[sign_x, sign_y]| Vec2::X * sign_x + Vec2::Y * sign_y)
402        .map(|reference_point| {
403            let scaling = Vec2::X * primitive.radius + Vec2::Y * primitive.half_length;
404            reference_point * scaling
405        })
406        .map(|vec2| isometry * vec2);
407
408        // draw left and right side of capsule "rectangle"
409        self.line_2d(bottom_left, top_left, polymorphic_color);
410        self.line_2d(bottom_right, top_right, polymorphic_color);
411
412        let start_angle_top = isometry.rotation.as_radians() - FRAC_PI_2;
413        let start_angle_bottom = isometry.rotation.as_radians() + FRAC_PI_2;
414
415        // draw arcs
416        self.arc_2d(
417            Isometry2d::new(top_center, Rot2::radians(start_angle_top)),
418            PI,
419            primitive.radius,
420            polymorphic_color,
421        );
422        self.arc_2d(
423            Isometry2d::new(bottom_center, Rot2::radians(start_angle_bottom)),
424            PI,
425            primitive.radius,
426            polymorphic_color,
427        );
428    }
429}
430
431// line 2d
432//
433/// Builder for configuring the drawing options of [`Line2d`].
434pub struct Line2dBuilder<'a, Config, Clear>
435where
436    Config: GizmoConfigGroup,
437    Clear: 'static + Send + Sync,
438{
439    gizmos: &'a mut GizmoBuffer<Config, Clear>,
440
441    direction: Dir2, // Direction of the line
442
443    isometry: Isometry2d,
444    color: Color, // color of the line
445
446    draw_arrow: bool, // decides whether to indicate the direction of the line with an arrow
447}
448
449impl<Config, Clear> Line2dBuilder<'_, Config, Clear>
450where
451    Config: GizmoConfigGroup,
452    Clear: 'static + Send + Sync,
453{
454    /// Set the drawing mode of the line (arrow vs. plain line)
455    pub fn draw_arrow(mut self, is_enabled: bool) -> Self {
456        self.draw_arrow = is_enabled;
457        self
458    }
459}
460
461impl<Config, Clear> GizmoPrimitive2d<Line2d> for GizmoBuffer<Config, Clear>
462where
463    Config: GizmoConfigGroup,
464    Clear: 'static + Send + Sync,
465{
466    type Output<'a>
467        = Line2dBuilder<'a, Config, Clear>
468    where
469        Self: 'a;
470
471    fn primitive_2d(
472        &mut self,
473        primitive: &Line2d,
474        isometry: impl Into<Isometry2d>,
475        color: impl Into<Color>,
476    ) -> Self::Output<'_> {
477        Line2dBuilder {
478            gizmos: self,
479            direction: primitive.direction,
480            isometry: isometry.into(),
481            color: color.into(),
482            draw_arrow: false,
483        }
484    }
485}
486
487impl<Config, Clear> Drop for Line2dBuilder<'_, Config, Clear>
488where
489    Config: GizmoConfigGroup,
490    Clear: 'static + Send + Sync,
491{
492    fn drop(&mut self) {
493        if !self.gizmos.enabled {
494            return;
495        }
496
497        let [start, end] = [1.0, -1.0]
498            .map(|sign| sign * INFINITE_LEN)
499            // offset the line from the origin infinitely into the given direction
500            .map(|length| self.direction * length)
501            // transform the line with the given isometry
502            .map(|offset| self.isometry * offset);
503
504        self.gizmos.line_2d(start, end, self.color);
505
506        // optionally draw an arrow head at the center of the line
507        if self.draw_arrow {
508            self.gizmos.arrow_2d(
509                self.isometry * (-self.direction * MIN_LINE_LEN),
510                self.isometry * Vec2::ZERO,
511                self.color,
512            );
513        }
514    }
515}
516
517// plane 2d
518
519impl<Config, Clear> GizmoPrimitive2d<Plane2d> for GizmoBuffer<Config, Clear>
520where
521    Config: GizmoConfigGroup,
522    Clear: 'static + Send + Sync,
523{
524    type Output<'a>
525        = ()
526    where
527        Self: 'a;
528
529    fn primitive_2d(
530        &mut self,
531        primitive: &Plane2d,
532        isometry: impl Into<Isometry2d>,
533        color: impl Into<Color>,
534    ) -> Self::Output<'_> {
535        let isometry = isometry.into();
536        let polymorphic_color: Color = color.into();
537
538        if !self.enabled {
539            return;
540        }
541        // draw normal of the plane (orthogonal to the plane itself)
542        let normal = primitive.normal;
543        let normal_segment = Segment2d::from_direction_and_length(normal, HALF_MIN_LINE_LEN * 2.);
544        self.primitive_2d(
545            &normal_segment,
546            // offset the normal so it starts on the plane line
547            Isometry2d::new(isometry * (HALF_MIN_LINE_LEN * normal), isometry.rotation),
548            polymorphic_color,
549        )
550        .draw_arrow(true);
551
552        // draw the plane line
553        let direction = Dir2::new_unchecked(-normal.perp());
554        self.primitive_2d(&Line2d { direction }, isometry, polymorphic_color)
555            .draw_arrow(false);
556
557        // draw an arrow such that the normal is always left side of the plane with respect to the
558        // planes direction. This is to follow the "counter-clockwise" convention
559        self.arrow_2d(
560            isometry * Vec2::ZERO,
561            isometry * (MIN_LINE_LEN * direction),
562            polymorphic_color,
563        );
564    }
565}
566
567// segment 2d
568
569/// Builder for configuring the drawing options of [`Segment2d`].
570pub struct Segment2dBuilder<'a, Config, Clear>
571where
572    Config: GizmoConfigGroup,
573    Clear: 'static + Send + Sync,
574{
575    gizmos: &'a mut GizmoBuffer<Config, Clear>,
576
577    point1: Vec2, // First point of the segment
578    point2: Vec2, // Second point of the segment
579
580    isometry: Isometry2d, // isometric transformation of the line segment
581    color: Color,         // color of the line segment
582
583    draw_arrow: bool, // decides whether to draw just a line or an arrow
584}
585
586impl<Config, Clear> Segment2dBuilder<'_, Config, Clear>
587where
588    Config: GizmoConfigGroup,
589    Clear: 'static + Send + Sync,
590{
591    /// Set the drawing mode of the line (arrow vs. plain line)
592    pub fn draw_arrow(mut self, is_enabled: bool) -> Self {
593        self.draw_arrow = is_enabled;
594        self
595    }
596}
597
598impl<Config, Clear> GizmoPrimitive2d<Segment2d> for GizmoBuffer<Config, Clear>
599where
600    Config: GizmoConfigGroup,
601    Clear: 'static + Send + Sync,
602{
603    type Output<'a>
604        = Segment2dBuilder<'a, Config, Clear>
605    where
606        Self: 'a;
607
608    fn primitive_2d(
609        &mut self,
610        primitive: &Segment2d,
611        isometry: impl Into<Isometry2d>,
612        color: impl Into<Color>,
613    ) -> Self::Output<'_> {
614        Segment2dBuilder {
615            gizmos: self,
616            point1: primitive.point1(),
617            point2: primitive.point2(),
618
619            isometry: isometry.into(),
620            color: color.into(),
621
622            draw_arrow: Default::default(),
623        }
624    }
625}
626
627impl<Config, Clear> Drop for Segment2dBuilder<'_, Config, Clear>
628where
629    Config: GizmoConfigGroup,
630    Clear: 'static + Send + Sync,
631{
632    fn drop(&mut self) {
633        if !self.gizmos.enabled {
634            return;
635        }
636
637        let segment = Segment2d::new(self.point1, self.point2).transformed(self.isometry);
638
639        if self.draw_arrow {
640            self.gizmos
641                .arrow_2d(segment.point1(), segment.point2(), self.color);
642        } else {
643            self.gizmos
644                .line_2d(segment.point1(), segment.point2(), self.color);
645        }
646    }
647}
648
649// polyline 2d
650
651impl<Config, Clear> GizmoPrimitive2d<Polyline2d> for GizmoBuffer<Config, Clear>
652where
653    Config: GizmoConfigGroup,
654    Clear: 'static + Send + Sync,
655{
656    type Output<'a>
657        = ()
658    where
659        Self: 'a;
660
661    fn primitive_2d(
662        &mut self,
663        primitive: &Polyline2d,
664        isometry: impl Into<Isometry2d>,
665        color: impl Into<Color>,
666    ) -> Self::Output<'_> {
667        if !self.enabled {
668            return;
669        }
670
671        let isometry = isometry.into();
672
673        self.linestrip_2d(
674            primitive
675                .vertices
676                .iter()
677                .copied()
678                .map(|vec2| isometry * vec2),
679            color,
680        );
681    }
682}
683
684// triangle 2d
685
686impl<Config, Clear> GizmoPrimitive2d<Triangle2d> for GizmoBuffer<Config, Clear>
687where
688    Config: GizmoConfigGroup,
689    Clear: 'static + Send + Sync,
690{
691    type Output<'a>
692        = ()
693    where
694        Self: 'a;
695
696    fn primitive_2d(
697        &mut self,
698        primitive: &Triangle2d,
699        isometry: impl Into<Isometry2d>,
700        color: impl Into<Color>,
701    ) -> Self::Output<'_> {
702        if !self.enabled {
703            return;
704        }
705
706        let isometry = isometry.into();
707
708        let [a, b, c] = primitive.vertices;
709        let positions = [a, b, c, a].map(|vec2| isometry * vec2);
710        self.linestrip_2d(positions, color);
711    }
712}
713
714// rectangle 2d
715
716impl<Config, Clear> GizmoPrimitive2d<Rectangle> for GizmoBuffer<Config, Clear>
717where
718    Config: GizmoConfigGroup,
719    Clear: 'static + Send + Sync,
720{
721    type Output<'a>
722        = ()
723    where
724        Self: 'a;
725
726    fn primitive_2d(
727        &mut self,
728        primitive: &Rectangle,
729        isometry: impl Into<Isometry2d>,
730        color: impl Into<Color>,
731    ) -> Self::Output<'_> {
732        if !self.enabled {
733            return;
734        }
735
736        let isometry = isometry.into();
737
738        let [a, b, c, d] =
739            [(1.0, 1.0), (1.0, -1.0), (-1.0, -1.0), (-1.0, 1.0)].map(|(sign_x, sign_y)| {
740                Vec2::new(
741                    primitive.half_size.x * sign_x,
742                    primitive.half_size.y * sign_y,
743                )
744            });
745        let positions = [a, b, c, d, a].map(|vec2| isometry * vec2);
746        self.linestrip_2d(positions, color);
747    }
748}
749
750// polygon 2d
751
752impl<Config, Clear> GizmoPrimitive2d<Polygon> for GizmoBuffer<Config, Clear>
753where
754    Config: GizmoConfigGroup,
755    Clear: 'static + Send + Sync,
756{
757    type Output<'a>
758        = ()
759    where
760        Self: 'a;
761
762    fn primitive_2d(
763        &mut self,
764        primitive: &Polygon,
765        isometry: impl Into<Isometry2d>,
766        color: impl Into<Color>,
767    ) -> Self::Output<'_> {
768        if !self.enabled {
769            return;
770        }
771
772        let isometry = isometry.into();
773
774        // Check if the polygon needs a closing point
775        let closing_point = {
776            let first = primitive.vertices.first();
777            (primitive.vertices.last() != first)
778                .then_some(first)
779                .flatten()
780                .cloned()
781        };
782
783        self.linestrip_2d(
784            primitive
785                .vertices
786                .iter()
787                .copied()
788                .chain(closing_point)
789                .map(|vec2| isometry * vec2),
790            color,
791        );
792    }
793}
794
795// regular polygon 2d
796
797impl<Config, Clear> GizmoPrimitive2d<RegularPolygon> for GizmoBuffer<Config, Clear>
798where
799    Config: GizmoConfigGroup,
800    Clear: 'static + Send + Sync,
801{
802    type Output<'a>
803        = ()
804    where
805        Self: 'a;
806
807    fn primitive_2d(
808        &mut self,
809        primitive: &RegularPolygon,
810        isometry: impl Into<Isometry2d>,
811        color: impl Into<Color>,
812    ) -> Self::Output<'_> {
813        if !self.enabled {
814            return;
815        }
816
817        let isometry = isometry.into();
818
819        let points = (0..=primitive.sides)
820            .map(|n| single_circle_coordinate(primitive.circumcircle.radius, primitive.sides, n))
821            .map(|vec2| isometry * vec2);
822        self.linestrip_2d(points, color);
823    }
824}