Skip to main content

poincare_lib/
style.rs

1use serde::{Deserialize, Deserializer, Serialize, Serializer};
2use viewport_lib::{
3    AttributeKind, BuiltinColourmap, BuiltinMatcap, ColourmapId, GlyphType, ParamVis,
4    ParamVisMode,
5};
6
7/// Transfer function for volume opacity control.
8#[derive(Debug, Clone, PartialEq)]
9pub struct TransferFunction {
10    /// Global opacity multiplier applied to all samples. Default: 0.5.
11    pub opacity_scale: f32,
12    /// Optional scalar threshold range `(min, max)`. Samples outside this range
13    /// are discarded (set to zero opacity). `None` means no threshold clipping.
14    pub threshold: Option<(f32, f32)>,
15}
16
17impl Default for TransferFunction {
18    fn default() -> Self {
19        Self {
20            opacity_scale: 0.5,
21            threshold: None,
22        }
23    }
24}
25
26#[derive(Serialize, Deserialize)]
27struct PersistedTransferFunction {
28    opacity_scale: f32,
29    threshold: Option<(f32, f32)>,
30}
31
32/// Colour source for a plot.
33#[derive(Debug, Clone, PartialEq)]
34pub enum ColourMode {
35    /// Fixed RGBA colour in linear 0..1.
36    Solid([f32; 4]),
37    /// Colour by the plot's default scalar field using a colormap.
38    Colormap {
39        /// Built-in or previously uploaded LUT.
40        colormap: ColormapSource,
41        /// Optional explicit scalar range. `None` lets the renderer auto-fit.
42        scalar_range: Option<(f32, f32)>,
43    },
44    /// Colour by a named scalar attribute.
45    ///
46    /// Surfaces expose `x`, `y`, `z`, `radius`, and `value`.
47    /// Non-surface plots also accept `x`, `y`, `z`, `radius`, `magnitude`, and `index`;
48    /// scatter plots with explicit scalars additionally accept `scalar` and `value`.
49    ByAttribute {
50        /// Name of the attribute as stored on the generated geometry.
51        name: String,
52        /// Attribute interpolation domain.
53        kind: AttributeKind,
54    },
55}
56
57#[derive(Serialize, Deserialize)]
58enum PersistedColourMode {
59    Solid([f32; 4]),
60    Colormap {
61        colormap: u8,
62        scalar_range: Option<(f32, f32)>,
63    },
64    ByAttribute {
65        name: String,
66        kind: u8,
67    },
68}
69
70impl Default for ColourMode {
71    fn default() -> Self {
72        Self::Solid([0.4, 0.6, 1.0, 1.0])
73    }
74}
75
76/// Colormap source used by [`ColourMode::Colormap`].
77#[derive(Debug, Clone, Copy, PartialEq, Eq)]
78pub enum ColormapSource {
79    /// One of the viewport's built-in LUT presets.
80    Builtin(BuiltinColourmap),
81    /// A caller-uploaded LUT.
82    Uploaded(ColourmapId),
83}
84
85impl Default for ColormapSource {
86    fn default() -> Self {
87        Self::Builtin(BuiltinColourmap::Viridis)
88    }
89}
90
91/// Surface shading model.
92#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
93pub enum ShadingMode {
94    /// One normal per triangle. Implemented for surfaces by expanding the mesh.
95    Flat,
96    /// Standard lit shading using per-vertex normals.
97    #[default]
98    Smooth,
99    /// Ignore scene lights and render with ambient-only colour.
100    Unlit,
101}
102
103/// Surface matcap source.
104#[derive(Debug, Clone, Copy, PartialEq, Eq)]
105pub enum MatcapSource {
106    /// One of the built-in viewport matcaps.
107    Builtin(BuiltinMatcap),
108}
109
110/// Preset face quantities generated for analytical surfaces.
111#[derive(Debug, Clone, Copy, PartialEq, Eq)]
112pub enum SurfaceFaceQuantity {
113    /// Sum of per-corner angle deviation between UV and world triangles.
114    AngleDistortion,
115    /// Triangle area ratio between world and UV domains.
116    AreaDistortion,
117}
118
119impl SurfaceFaceQuantity {
120    /// Stable attribute key used in `MeshData::attributes`.
121    pub fn attribute_name(self) -> &'static str {
122        match self {
123            Self::AngleDistortion => "angle_distortion",
124            Self::AreaDistortion => "area_distortion",
125        }
126    }
127}
128
129/// Surface UV visualization presets.
130#[derive(Debug, Clone, Copy, PartialEq)]
131pub struct ParamVisSettings {
132    /// Which procedural UV pattern to show.
133    pub mode: ParamVisMode,
134    /// Tile frequency multiplier.
135    pub scale: f32,
136}
137
138#[derive(Serialize, Deserialize)]
139struct PersistedParamVis {
140    mode: u8,
141    scale: f32,
142}
143
144impl Default for ParamVisSettings {
145    fn default() -> Self {
146        Self {
147            mode: ParamVisMode::Checker,
148            scale: 8.0,
149        }
150    }
151}
152
153impl From<ParamVisSettings> for ParamVis {
154    fn from(value: ParamVisSettings) -> Self {
155        Self {
156            mode: value.mode,
157            scale: value.scale,
158        }
159    }
160}
161
162/// Built-in vector fields available for surface LIC rendering.
163#[derive(Debug, Clone, Copy, PartialEq, Eq)]
164pub enum SurfaceLicVectorField {
165    /// Follow the surface tangent in the increasing U direction.
166    TangentU,
167    /// Follow the surface tangent in the increasing V direction.
168    TangentV,
169    /// Diagonal flow: normalized sum of TangentU and TangentV.
170    /// Produces helical / winding streaks across both parametric directions.
171    Diagonal,
172    /// Saddle flow: normalized difference of TangentU and TangentV.
173    /// Creates saddle-point topology — flow converges along U and diverges along V.
174    Saddle,
175}
176
177impl SurfaceLicVectorField {
178    /// Stable mesh attribute name used by the viewport LIC pass.
179    pub fn attribute_name(self) -> &'static str {
180        match self {
181            Self::TangentU => "tangent_u",
182            Self::TangentV => "tangent_v",
183            Self::Diagonal => "tangent_diagonal",
184            Self::Saddle => "tangent_saddle",
185        }
186    }
187}
188
189/// Surface LIC configuration carried on plot styles.
190#[derive(Debug, Clone, PartialEq)]
191pub struct SurfaceLicSettings {
192    /// Which per-vertex vector field to advect along.
193    pub vector_field: SurfaceLicVectorField,
194    /// Number of forward/backward advection steps.
195    pub steps: u32,
196    /// Screen-space step size in pixels.
197    pub step_size: f32,
198    /// Contrast / modulation strength.
199    pub strength: f32,
200}
201
202#[derive(Serialize, Deserialize)]
203struct PersistedSurfaceLic {
204    vector_field: u8,
205    steps: u32,
206    step_size: f32,
207    strength: f32,
208}
209
210impl Default for SurfaceLicSettings {
211    fn default() -> Self {
212        Self {
213            vector_field: SurfaceLicVectorField::TangentU,
214            steps: 20,
215            step_size: 1.5,
216            strength: 2.0,
217        }
218    }
219}
220
221/// Visual appearance of a plot object.
222#[derive(Debug, Clone, PartialEq)]
223pub struct PlotStyle {
224    /// Colour source. Default: light blue solid fill.
225    pub colour_mode: ColourMode,
226    /// Global opacity multiplier. Default: 1.0.
227    pub opacity: f32,
228    /// Whether mesh surfaces should render without backface culling. Default: false.
229    pub two_sided: bool,
230    /// Hardware line width in pixels for curves. Default: 2.0.
231    pub line_width: f32,
232    /// Screen-space point size in pixels for Scatter3D. Default: 4.0.
233    pub point_size: f32,
234    /// Global scale applied to glyph arrows in VectorField3D. Default: 1.0.
235    pub glyph_scale: f32,
236    /// Glyph mesh used for vector field instances. Default: arrow.
237    pub glyph_type: GlyphType,
238    /// Surface shading mode. Default: smooth.
239    pub shading: ShadingMode,
240    /// Tube radius for StreamPlot3D. When `Some(r)`, streamlines are rendered as
241    /// solid tubes with radius `r` instead of polylines. Default: `None`.
242    pub tube_radius: Option<f32>,
243    /// Transfer function for volume opacity control used by DensityPlot3D.
244    /// `None` uses a default linear ramp with `opacity_scale = 0.5`.
245    pub transfer_function: Option<TransferFunction>,
246    /// Optional built-in matcap preset for surface rendering.
247    pub matcap: Option<MatcapSource>,
248    /// Optional procedural UV visualization for surfaces.
249    pub param_vis: Option<ParamVisSettings>,
250    /// Optional face quantity to color analytical surfaces by.
251    pub face_quantity: Option<SurfaceFaceQuantity>,
252    /// Optional surface line integral convolution overlay.
253    pub surface_lic: Option<SurfaceLicSettings>,
254}
255
256#[derive(Serialize, Deserialize)]
257struct PersistedPlotStyle {
258    colour_mode: PersistedColourMode,
259    opacity: f32,
260    two_sided: bool,
261    line_width: f32,
262    point_size: f32,
263    glyph_scale: f32,
264    glyph_type: u8,
265    shading: u8,
266    tube_radius: Option<f32>,
267    transfer_function: Option<PersistedTransferFunction>,
268    matcap: Option<u8>,
269    param_vis: Option<PersistedParamVis>,
270    face_quantity: Option<u8>,
271    surface_lic: Option<PersistedSurfaceLic>,
272}
273
274impl Default for PlotStyle {
275    fn default() -> Self {
276        Self {
277            colour_mode: ColourMode::default(),
278            opacity: 1.0,
279            two_sided: false,
280            line_width: 2.0,
281            point_size: 4.0,
282            glyph_scale: 1.0,
283            glyph_type: GlyphType::Arrow,
284            shading: ShadingMode::Smooth,
285            tube_radius: None,
286            transfer_function: None,
287            matcap: None,
288            param_vis: None,
289            face_quantity: None,
290            surface_lic: None,
291        }
292    }
293}
294
295impl Serialize for TransferFunction {
296    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
297    where
298        S: Serializer,
299    {
300        PersistedTransferFunction {
301            opacity_scale: self.opacity_scale,
302            threshold: self.threshold,
303        }
304        .serialize(serializer)
305    }
306}
307
308impl<'de> Deserialize<'de> for TransferFunction {
309    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
310    where
311        D: Deserializer<'de>,
312    {
313        let persisted = PersistedTransferFunction::deserialize(deserializer)?;
314        Ok(Self {
315            opacity_scale: persisted.opacity_scale,
316            threshold: persisted.threshold,
317        })
318    }
319}
320
321impl Serialize for ColourMode {
322    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
323    where
324        S: Serializer,
325    {
326        let persisted = match self {
327            Self::Solid(rgba) => PersistedColourMode::Solid(*rgba),
328            Self::Colormap {
329                colormap,
330                scalar_range,
331            } => PersistedColourMode::Colormap {
332                colormap: match colormap {
333                    ColormapSource::Builtin(preset) => builtin_colormap_to_u8(*preset),
334                    ColormapSource::Uploaded(_) => builtin_colormap_to_u8(BuiltinColourmap::Viridis),
335                },
336                scalar_range: *scalar_range,
337            },
338            Self::ByAttribute { name, kind } => PersistedColourMode::ByAttribute {
339                name: name.clone(),
340                kind: attribute_kind_to_u8(*kind),
341            },
342        };
343        persisted.serialize(serializer)
344    }
345}
346
347impl<'de> Deserialize<'de> for ColourMode {
348    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
349    where
350        D: Deserializer<'de>,
351    {
352        let persisted = PersistedColourMode::deserialize(deserializer)?;
353        Ok(match persisted {
354            PersistedColourMode::Solid(rgba) => Self::Solid(rgba),
355            PersistedColourMode::Colormap {
356                colormap,
357                scalar_range,
358            } => Self::Colormap {
359                colormap: ColormapSource::Builtin(u8_to_builtin_colormap(colormap)),
360                scalar_range,
361            },
362            PersistedColourMode::ByAttribute { name, kind } => Self::ByAttribute {
363                name,
364                kind: u8_to_attribute_kind(kind),
365            },
366        })
367    }
368}
369
370impl Serialize for ParamVisSettings {
371    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
372    where
373        S: Serializer,
374    {
375        PersistedParamVis {
376            mode: param_vis_mode_to_u8(self.mode),
377            scale: self.scale,
378        }
379        .serialize(serializer)
380    }
381}
382
383impl<'de> Deserialize<'de> for ParamVisSettings {
384    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
385    where
386        D: Deserializer<'de>,
387    {
388        let persisted = PersistedParamVis::deserialize(deserializer)?;
389        Ok(Self {
390            mode: u8_to_param_vis_mode(persisted.mode),
391            scale: persisted.scale,
392        })
393    }
394}
395
396impl Serialize for SurfaceLicSettings {
397    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
398    where
399        S: Serializer,
400    {
401        PersistedSurfaceLic {
402            vector_field: surface_lic_vector_field_to_u8(self.vector_field),
403            steps: self.steps,
404            step_size: self.step_size,
405            strength: self.strength,
406        }
407        .serialize(serializer)
408    }
409}
410
411impl<'de> Deserialize<'de> for SurfaceLicSettings {
412    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
413    where
414        D: Deserializer<'de>,
415    {
416        let persisted = PersistedSurfaceLic::deserialize(deserializer)?;
417        Ok(Self {
418            vector_field: u8_to_surface_lic_vector_field(persisted.vector_field),
419            steps: persisted.steps,
420            step_size: persisted.step_size,
421            strength: persisted.strength,
422        })
423    }
424}
425
426impl Serialize for PlotStyle {
427    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
428    where
429        S: Serializer,
430    {
431        PersistedPlotStyle {
432            colour_mode: match &self.colour_mode {
433                ColourMode::Solid(rgba) => PersistedColourMode::Solid(*rgba),
434                ColourMode::Colormap {
435                    colormap,
436                    scalar_range,
437                } => PersistedColourMode::Colormap {
438                    colormap: match colormap {
439                        ColormapSource::Builtin(preset) => builtin_colormap_to_u8(*preset),
440                        ColormapSource::Uploaded(_) => builtin_colormap_to_u8(BuiltinColourmap::Viridis),
441                    },
442                    scalar_range: *scalar_range,
443                },
444                ColourMode::ByAttribute { name, kind } => PersistedColourMode::ByAttribute {
445                    name: name.clone(),
446                    kind: attribute_kind_to_u8(*kind),
447                },
448            },
449            opacity: self.opacity,
450            two_sided: self.two_sided,
451            line_width: self.line_width,
452            point_size: self.point_size,
453            glyph_scale: self.glyph_scale,
454            glyph_type: glyph_type_to_u8(self.glyph_type),
455            shading: shading_to_u8(self.shading),
456            tube_radius: self.tube_radius,
457            transfer_function: self
458                .transfer_function
459                .as_ref()
460                .map(|tf| PersistedTransferFunction {
461                    opacity_scale: tf.opacity_scale,
462                    threshold: tf.threshold,
463                }),
464            matcap: self.matcap.map(|m| match m {
465                MatcapSource::Builtin(preset) => builtin_matcap_to_u8(preset),
466            }),
467            param_vis: self.param_vis.map(|pv| PersistedParamVis {
468                mode: param_vis_mode_to_u8(pv.mode),
469                scale: pv.scale,
470            }),
471            face_quantity: self.face_quantity.map(surface_face_quantity_to_u8),
472            surface_lic: self.surface_lic.as_ref().map(|lic| PersistedSurfaceLic {
473                vector_field: surface_lic_vector_field_to_u8(lic.vector_field),
474                steps: lic.steps,
475                step_size: lic.step_size,
476                strength: lic.strength,
477            }),
478        }
479        .serialize(serializer)
480    }
481}
482
483impl<'de> Deserialize<'de> for PlotStyle {
484    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
485    where
486        D: Deserializer<'de>,
487    {
488        let persisted = PersistedPlotStyle::deserialize(deserializer)?;
489        Ok(Self {
490            colour_mode: match persisted.colour_mode {
491                PersistedColourMode::Solid(rgba) => ColourMode::Solid(rgba),
492                PersistedColourMode::Colormap {
493                    colormap,
494                    scalar_range,
495                } => ColourMode::Colormap {
496                    colormap: ColormapSource::Builtin(u8_to_builtin_colormap(colormap)),
497                    scalar_range,
498                },
499                PersistedColourMode::ByAttribute { name, kind } => ColourMode::ByAttribute {
500                    name,
501                    kind: u8_to_attribute_kind(kind),
502                },
503            },
504            opacity: persisted.opacity,
505            two_sided: persisted.two_sided,
506            line_width: persisted.line_width,
507            point_size: persisted.point_size,
508            glyph_scale: persisted.glyph_scale,
509            glyph_type: u8_to_glyph_type(persisted.glyph_type),
510            shading: u8_to_shading(persisted.shading),
511            tube_radius: persisted.tube_radius,
512            transfer_function: persisted.transfer_function.map(|tf| TransferFunction {
513                opacity_scale: tf.opacity_scale,
514                threshold: tf.threshold,
515            }),
516            matcap: persisted
517                .matcap
518                .map(|m| MatcapSource::Builtin(u8_to_builtin_matcap(m))),
519            param_vis: persisted.param_vis.map(|pv| ParamVisSettings {
520                mode: u8_to_param_vis_mode(pv.mode),
521                scale: pv.scale,
522            }),
523            face_quantity: persisted.face_quantity.map(u8_to_surface_face_quantity),
524            surface_lic: persisted.surface_lic.map(|lic| SurfaceLicSettings {
525                vector_field: u8_to_surface_lic_vector_field(lic.vector_field),
526                steps: lic.steps,
527                step_size: lic.step_size,
528                strength: lic.strength,
529            }),
530        })
531    }
532}
533
534fn builtin_colormap_to_u8(value: BuiltinColourmap) -> u8 {
535    match value {
536        BuiltinColourmap::Viridis => 0,
537        BuiltinColourmap::Plasma => 1,
538        BuiltinColourmap::Greyscale => 2,
539        BuiltinColourmap::Coolwarm => 3,
540        BuiltinColourmap::Rainbow => 4,
541        BuiltinColourmap::Magma => 5,
542        BuiltinColourmap::Inferno => 6,
543        BuiltinColourmap::Turbo => 7,
544        BuiltinColourmap::Jet => 8,
545        BuiltinColourmap::RdBu => 9,
546    }
547}
548
549fn u8_to_builtin_colormap(value: u8) -> BuiltinColourmap {
550    match value {
551        1 => BuiltinColourmap::Plasma,
552        2 => BuiltinColourmap::Greyscale,
553        3 => BuiltinColourmap::Coolwarm,
554        4 => BuiltinColourmap::Rainbow,
555        5 => BuiltinColourmap::Magma,
556        6 => BuiltinColourmap::Inferno,
557        7 => BuiltinColourmap::Turbo,
558        8 => BuiltinColourmap::Jet,
559        9 => BuiltinColourmap::RdBu,
560        _ => BuiltinColourmap::Viridis,
561    }
562}
563
564fn attribute_kind_to_u8(value: AttributeKind) -> u8 {
565    match value {
566        AttributeKind::Vertex => 0,
567        AttributeKind::Cell => 1,
568        AttributeKind::Face => 2,
569        AttributeKind::FaceColour => 3,
570        AttributeKind::Edge => 4,
571        AttributeKind::Halfedge => 5,
572        AttributeKind::Corner => 6,
573    }
574}
575
576fn u8_to_attribute_kind(value: u8) -> AttributeKind {
577    match value {
578        1 => AttributeKind::Cell,
579        2 => AttributeKind::Face,
580        3 => AttributeKind::FaceColour,
581        4 => AttributeKind::Edge,
582        5 => AttributeKind::Halfedge,
583        6 => AttributeKind::Corner,
584        _ => AttributeKind::Vertex,
585    }
586}
587
588fn shading_to_u8(value: ShadingMode) -> u8 {
589    match value {
590        ShadingMode::Flat => 0,
591        ShadingMode::Smooth => 1,
592        ShadingMode::Unlit => 2,
593    }
594}
595
596fn u8_to_shading(value: u8) -> ShadingMode {
597    match value {
598        0 => ShadingMode::Flat,
599        2 => ShadingMode::Unlit,
600        _ => ShadingMode::Smooth,
601    }
602}
603
604fn glyph_type_to_u8(value: GlyphType) -> u8 {
605    match value {
606        GlyphType::Arrow => 0,
607        GlyphType::Sphere => 1,
608        GlyphType::Cube => 2,
609    }
610}
611
612fn u8_to_glyph_type(value: u8) -> GlyphType {
613    match value {
614        1 => GlyphType::Sphere,
615        2 => GlyphType::Cube,
616        _ => GlyphType::Arrow,
617    }
618}
619
620fn builtin_matcap_to_u8(value: BuiltinMatcap) -> u8 {
621    value as u8
622}
623
624fn u8_to_builtin_matcap(value: u8) -> BuiltinMatcap {
625    match value {
626        1 => BuiltinMatcap::Wax,
627        2 => BuiltinMatcap::Candy,
628        3 => BuiltinMatcap::Flat,
629        4 => BuiltinMatcap::Ceramic,
630        5 => BuiltinMatcap::Jade,
631        6 => BuiltinMatcap::Mud,
632        7 => BuiltinMatcap::Normal,
633        _ => BuiltinMatcap::Clay,
634    }
635}
636
637fn param_vis_mode_to_u8(value: ParamVisMode) -> u8 {
638    match value {
639        ParamVisMode::Checker => 0,
640        ParamVisMode::Grid => 1,
641        ParamVisMode::LocalChecker => 2,
642        ParamVisMode::LocalRadial => 3,
643    }
644}
645
646fn u8_to_param_vis_mode(value: u8) -> ParamVisMode {
647    match value {
648        1 => ParamVisMode::Grid,
649        2 => ParamVisMode::LocalChecker,
650        3 => ParamVisMode::LocalRadial,
651        _ => ParamVisMode::Checker,
652    }
653}
654
655fn surface_face_quantity_to_u8(value: SurfaceFaceQuantity) -> u8 {
656    match value {
657        SurfaceFaceQuantity::AngleDistortion => 0,
658        SurfaceFaceQuantity::AreaDistortion => 1,
659    }
660}
661
662fn u8_to_surface_face_quantity(value: u8) -> SurfaceFaceQuantity {
663    match value {
664        1 => SurfaceFaceQuantity::AreaDistortion,
665        _ => SurfaceFaceQuantity::AngleDistortion,
666    }
667}
668
669fn surface_lic_vector_field_to_u8(value: SurfaceLicVectorField) -> u8 {
670    match value {
671        SurfaceLicVectorField::TangentU => 0,
672        SurfaceLicVectorField::TangentV => 1,
673        SurfaceLicVectorField::Diagonal => 2,
674        SurfaceLicVectorField::Saddle => 3,
675    }
676}
677
678fn u8_to_surface_lic_vector_field(value: u8) -> SurfaceLicVectorField {
679    match value {
680        1 => SurfaceLicVectorField::TangentV,
681        2 => SurfaceLicVectorField::Diagonal,
682        3 => SurfaceLicVectorField::Saddle,
683        _ => SurfaceLicVectorField::TangentU,
684    }
685}