Skip to main content

cityjson_types/resources/mapping/
textures.rs

1use crate::resources::handles::TextureHandle;
2use crate::resources::id::{ResourceId, ResourceId32};
3use crate::v2_0::vertex::{VertexIndex, VertexRef};
4
5#[repr(C)]
6#[derive(Clone, Debug, Default, Hash, Ord, PartialOrd, Eq, PartialEq)]
7pub(crate) struct TextureMapCore<VR: VertexRef, RR: ResourceId> {
8    vertices: Vec<Option<VertexIndex<VR>>>,
9    rings: Vec<VertexIndex<VR>>,
10    ring_textures: Vec<Option<RR>>,
11    // The boundary is authoritative for surface, shell, and solid topology.
12}
13
14impl<VR: VertexRef, RR: ResourceId> TextureMapCore<VR, RR> {
15    pub(crate) fn is_empty(&self) -> bool {
16        self.vertices.is_empty() && self.rings.is_empty() && self.ring_textures.is_empty()
17    }
18
19    pub(crate) fn add_vertex(&mut self, vertex: Option<VertexIndex<VR>>) {
20        self.vertices.push(vertex);
21    }
22
23    pub(crate) fn add_ring(&mut self, ring_start: VertexIndex<VR>) {
24        self.rings.push(ring_start);
25    }
26
27    pub(crate) fn add_ring_texture(&mut self, texture: Option<RR>) {
28        self.ring_textures.push(texture);
29    }
30
31    pub(crate) fn vertices(&self) -> &[Option<VertexIndex<VR>>] {
32        &self.vertices
33    }
34
35    pub(crate) fn vertices_mut(&mut self) -> &mut [Option<VertexIndex<VR>>] {
36        &mut self.vertices
37    }
38
39    pub(crate) fn rings(&self) -> &[VertexIndex<VR>] {
40        &self.rings
41    }
42
43    pub(crate) fn rings_mut(&mut self) -> &mut [VertexIndex<VR>] {
44        &mut self.rings
45    }
46
47    pub(crate) fn ring_textures(&self) -> &[Option<RR>] {
48        &self.ring_textures
49    }
50
51    pub(crate) fn ring_textures_mut(&mut self) -> &mut [Option<RR>] {
52        &mut self.ring_textures
53    }
54}
55
56#[repr(C)]
57#[derive(Clone, Debug, Default, Hash, Ord, PartialOrd, Eq, PartialEq)]
58pub struct TextureMap<VR: VertexRef> {
59    inner: TextureMapCore<VR, ResourceId32>,
60}
61
62impl<VR: VertexRef> TextureMap<VR> {
63    #[must_use]
64    pub fn new() -> Self {
65        Self::default()
66    }
67
68    #[must_use]
69    pub fn is_empty(&self) -> bool {
70        self.inner.is_empty()
71    }
72
73    pub fn add_vertex(&mut self, vertex: Option<VertexIndex<VR>>) {
74        self.inner.add_vertex(vertex);
75    }
76
77    pub fn add_ring(&mut self, ring_start: VertexIndex<VR>) {
78        self.inner.add_ring(ring_start);
79    }
80
81    pub fn add_ring_texture(&mut self, texture: Option<TextureHandle>) {
82        self.inner
83            .add_ring_texture(texture.map(super::super::handles::TextureHandle::to_raw));
84    }
85
86    #[must_use]
87    pub fn vertices(&self) -> &[Option<VertexIndex<VR>>] {
88        self.inner.vertices()
89    }
90
91    pub fn vertices_mut(&mut self) -> &mut [Option<VertexIndex<VR>>] {
92        self.inner.vertices_mut()
93    }
94
95    #[must_use]
96    pub fn rings(&self) -> &[VertexIndex<VR>] {
97        self.inner.rings()
98    }
99
100    pub fn rings_mut(&mut self) -> &mut [VertexIndex<VR>] {
101        self.inner.rings_mut()
102    }
103
104    #[must_use]
105    pub fn ring_textures(&self) -> Vec<Option<TextureHandle>> {
106        self.inner
107            .ring_textures()
108            .iter()
109            .copied()
110            .map(|r| r.map(TextureHandle::from_raw))
111            .collect()
112    }
113
114    pub fn set_ring_texture(&mut self, ring_index: usize, texture: Option<TextureHandle>) -> bool {
115        let Some(slot) = self.inner.ring_textures_mut().get_mut(ring_index) else {
116            return false;
117        };
118        *slot = texture.map(super::super::handles::TextureHandle::to_raw);
119        true
120    }
121
122    #[allow(dead_code)]
123    pub(crate) fn from_raw(inner: TextureMapCore<VR, ResourceId32>) -> Self {
124        Self { inner }
125    }
126
127    #[allow(dead_code)]
128    pub(crate) fn into_raw(self) -> TextureMapCore<VR, ResourceId32> {
129        self.inner
130    }
131
132    #[allow(dead_code)]
133    pub(crate) fn to_raw(&self) -> &TextureMapCore<VR, ResourceId32> {
134        &self.inner
135    }
136}
137
138// ---------------------------------------------------------------------------
139// Unit tests for TextureMapCore / TextureMap
140// Family 8: texture topology (vertices / rings / ring_textures)
141// Family 9: vertex-reuse — same geometric vertex, different UV per ring
142// ---------------------------------------------------------------------------
143
144#[cfg(test)]
145mod texture_map {
146    use super::*;
147    use crate::resources::id::ResourceId32;
148
149    type Core = TextureMapCore<u32, ResourceId32>;
150
151    fn make_tex_id(index: u32) -> ResourceId32 {
152        ResourceId32::new(index, 0)
153    }
154
155    fn vi(n: u32) -> VertexIndex<u32> {
156        VertexIndex::new(n)
157    }
158
159    // -----------------------------------------------------------------------
160    // Family 8: core topology
161    // -----------------------------------------------------------------------
162
163    #[test]
164    fn ring_count_matches_uv_vertex_count() {
165        // A ring with 3 vertices: ring starts at uv-vertex index 0
166        let mut core = Core::default();
167        core.add_vertex(Some(vi(0)));
168        core.add_vertex(Some(vi(1)));
169        core.add_vertex(Some(vi(2)));
170        core.add_ring(vi(0));
171        core.add_ring_texture(Some(make_tex_id(0)));
172
173        assert_eq!(core.vertices().len(), 3);
174        assert_eq!(core.rings().len(), 1);
175        assert_eq!(core.ring_textures().len(), 1);
176    }
177
178    #[test]
179    fn ring_texture_can_be_none() {
180        let mut core = Core::default();
181        core.add_vertex(Some(vi(0)));
182        core.add_ring(vi(0));
183        core.add_ring_texture(None);
184
185        assert!(core.ring_textures()[0].is_none());
186    }
187
188    #[test]
189    fn multi_ring_multi_texture() {
190        // Two rings in the same surface, each with a different texture
191        let mut core = Core::default();
192        // Ring 0: vertices 0,1,2
193        core.add_vertex(Some(vi(0)));
194        core.add_vertex(Some(vi(1)));
195        core.add_vertex(Some(vi(2)));
196        core.add_ring(vi(0));
197        core.add_ring_texture(Some(make_tex_id(0)));
198        // Ring 1: vertices 0,2,3
199        core.add_vertex(Some(vi(0)));
200        core.add_vertex(Some(vi(2)));
201        core.add_vertex(Some(vi(3)));
202        core.add_ring(vi(3));
203        core.add_ring_texture(Some(make_tex_id(1)));
204
205        assert_eq!(core.vertices().len(), 6);
206        assert_eq!(core.rings().len(), 2);
207        assert_eq!(core.ring_textures().len(), 2);
208        assert_eq!(core.ring_textures()[0], Some(make_tex_id(0)));
209        assert_eq!(core.ring_textures()[1], Some(make_tex_id(1)));
210    }
211
212    // -----------------------------------------------------------------------
213    // Family 9: vertex-reuse — a geometric vertex can appear in multiple rings
214    // with different UV coordinates per occurrence
215    // -----------------------------------------------------------------------
216
217    #[test]
218    fn vertex_reuse_different_uvs_per_ring() {
219        // Geometric vertices 0 and 1 appear in two rings.
220        // Ring 0 maps vertex 0 → uv-vertex 0, vertex 1 → uv-vertex 1.
221        // Ring 1 maps vertex 0 → uv-vertex 2 (different UV!), vertex 1 → uv-vertex 3.
222        let mut core = Core::default();
223        // Ring 0 uv-vertices
224        core.add_vertex(Some(vi(0))); // uv index 0 → geom vertex 0
225        core.add_vertex(Some(vi(1))); // uv index 1 → geom vertex 1
226        core.add_ring(vi(0));
227        core.add_ring_texture(Some(make_tex_id(0)));
228        // Ring 1 uv-vertices — same geom vertices, different UV positions in the pool
229        core.add_vertex(Some(vi(0))); // uv index 2 → geom vertex 0 (reused)
230        core.add_vertex(Some(vi(1))); // uv index 3 → geom vertex 1 (reused)
231        core.add_ring(vi(2));
232        core.add_ring_texture(Some(make_tex_id(0)));
233
234        assert_eq!(core.vertices().len(), 4, "4 uv-vertex slots (2 per ring)");
235        // uv-vertex 0 and uv-vertex 2 both reference the same geometric vertex 0
236        assert_eq!(core.vertices()[0], core.vertices()[2]);
237        // but they are distinct slots — allowing different UV coords in the UV pool
238        assert_ne!(
239            core.rings()[0],
240            core.rings()[1],
241            "rings start at different uv-vertex offsets"
242        );
243    }
244}