use crate::backend::default::geometry::GeometryInstanceData;
use crate::cityjson::core::appearance::ThemeName;
use crate::error::{Error, Result};
use crate::resources::handles::{
GeometryHandle, GeometryTemplateHandle, MaterialHandle, SemanticHandle, TextureHandle,
};
use crate::resources::id::ResourceId32;
use crate::resources::mapping::SemanticOrMaterialMap;
use crate::resources::mapping::textures::TextureMapCore;
use crate::resources::storage::StringStorage;
use crate::v2_0::Boundary;
use crate::v2_0::citymodel::CityModel;
use crate::v2_0::coordinate::{RealWorldCoordinate, UVCoordinate};
use crate::v2_0::geometry::{AffineTransform3D, Geometry, GeometryType, LoD};
use crate::v2_0::vertex::{VertexIndex, VertexRef};
use std::collections::{HashMap, HashSet};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum DraftInsertMode {
Regular,
Template,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
struct RealWorldCoordinateKey([u64; 3]);
impl From<RealWorldCoordinate> for RealWorldCoordinateKey {
fn from(value: RealWorldCoordinate) -> Self {
Self([
value.x().to_bits(),
value.y().to_bits(),
value.z().to_bits(),
])
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
struct UvCoordinateKey([u32; 2]);
impl From<&UVCoordinate> for UvCoordinateKey {
fn from(value: &UVCoordinate) -> Self {
Self([value.u().to_bits(), value.v().to_bits()])
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum VertexDraft<VR: VertexRef> {
Existing(VertexIndex<VR>),
New(RealWorldCoordinate),
}
impl<VR: VertexRef> From<VertexIndex<VR>> for VertexDraft<VR> {
fn from(value: VertexIndex<VR>) -> Self {
Self::Existing(value)
}
}
impl<VR: VertexRef> From<RealWorldCoordinate> for VertexDraft<VR> {
fn from(value: RealWorldCoordinate) -> Self {
Self::New(value)
}
}
impl<VR: VertexRef> From<[f64; 3]> for VertexDraft<VR> {
fn from(value: [f64; 3]) -> Self {
Self::New(RealWorldCoordinate::from(value))
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum UvDraft<VR: VertexRef> {
Existing(VertexIndex<VR>),
New(UVCoordinate),
}
impl<VR: VertexRef> From<VertexIndex<VR>> for UvDraft<VR> {
fn from(value: VertexIndex<VR>) -> Self {
Self::Existing(value)
}
}
impl<VR: VertexRef> From<UVCoordinate> for UvDraft<VR> {
fn from(value: UVCoordinate) -> Self {
Self::New(value)
}
}
impl<VR: VertexRef> From<[f32; 2]> for UvDraft<VR> {
fn from(value: [f32; 2]) -> Self {
Self::New(UVCoordinate::new(value[0], value[1]))
}
}
#[derive(Clone, Debug, PartialEq)]
struct RingTextureDraft<VR: VertexRef, SS: StringStorage> {
theme: ThemeName<SS>,
texture: TextureHandle,
uvs: Vec<UvDraft<VR>>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct PointDraft<VR: VertexRef> {
vertex: VertexDraft<VR>,
semantic: Option<SemanticHandle>,
}
impl<VR: VertexRef> PointDraft<VR> {
pub fn new<T>(vertex: T) -> Self
where
T: Into<VertexDraft<VR>>,
{
Self {
vertex: vertex.into(),
semantic: None,
}
}
#[must_use]
pub fn with_semantic(mut self, semantic: SemanticHandle) -> Self {
self.semantic = Some(semantic);
self
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct LineStringDraft<VR: VertexRef> {
vertices: Vec<VertexDraft<VR>>,
semantic: Option<SemanticHandle>,
}
impl<VR: VertexRef> LineStringDraft<VR> {
pub fn new<I, T>(vertices: I) -> Self
where
I: IntoIterator<Item = T>,
T: Into<VertexDraft<VR>>,
{
Self {
vertices: vertices.into_iter().map(Into::into).collect(),
semantic: None,
}
}
#[must_use]
pub fn with_semantic(mut self, semantic: SemanticHandle) -> Self {
self.semantic = Some(semantic);
self
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct RingDraft<VR: VertexRef, SS: StringStorage> {
vertices: Vec<VertexDraft<VR>>,
textures: Vec<RingTextureDraft<VR, SS>>,
}
impl<VR: VertexRef, SS: StringStorage> RingDraft<VR, SS> {
pub fn new<I, T>(vertices: I) -> Self
where
I: IntoIterator<Item = T>,
T: Into<VertexDraft<VR>>,
{
Self {
vertices: vertices.into_iter().map(Into::into).collect(),
textures: Vec::new(),
}
}
#[must_use]
pub fn with_texture<I, T>(
mut self,
theme: impl Into<ThemeName<SS>>,
texture: TextureHandle,
uvs: I,
) -> Self
where
I: IntoIterator<Item = T>,
T: Into<UvDraft<VR>>,
{
self.textures.push(RingTextureDraft {
theme: theme.into(),
texture,
uvs: uvs.into_iter().map(Into::into).collect(),
});
self
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct SurfaceDraft<VR: VertexRef, SS: StringStorage> {
outer: RingDraft<VR, SS>,
inners: Vec<RingDraft<VR, SS>>,
semantic: Option<SemanticHandle>,
materials: Vec<(ThemeName<SS>, MaterialHandle)>,
}
impl<VR: VertexRef, SS: StringStorage> SurfaceDraft<VR, SS> {
pub fn new<I>(outer: RingDraft<VR, SS>, inners: I) -> Self
where
I: IntoIterator<Item = RingDraft<VR, SS>>,
{
Self {
outer,
inners: inners.into_iter().collect(),
semantic: None,
materials: Vec::new(),
}
}
#[must_use]
pub fn with_semantic(mut self, semantic: SemanticHandle) -> Self {
self.semantic = Some(semantic);
self
}
#[must_use]
pub fn with_material(
mut self,
theme: impl Into<ThemeName<SS>>,
material: MaterialHandle,
) -> Self {
self.materials.push((theme.into(), material));
self
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct ShellDraft<VR: VertexRef, SS: StringStorage> {
surfaces: Vec<SurfaceDraft<VR, SS>>,
}
impl<VR: VertexRef, SS: StringStorage> ShellDraft<VR, SS> {
pub fn new<I>(surfaces: I) -> Self
where
I: IntoIterator<Item = SurfaceDraft<VR, SS>>,
{
Self {
surfaces: surfaces.into_iter().collect(),
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct SolidDraft<VR: VertexRef, SS: StringStorage> {
outer: ShellDraft<VR, SS>,
inners: Vec<ShellDraft<VR, SS>>,
}
impl<VR: VertexRef, SS: StringStorage> SolidDraft<VR, SS> {
pub fn new<I>(outer: ShellDraft<VR, SS>, inners: I) -> Self
where
I: IntoIterator<Item = ShellDraft<VR, SS>>,
{
Self {
outer,
inners: inners.into_iter().collect(),
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum GeometryDraft<VR: VertexRef, SS: StringStorage> {
MultiPoint {
lod: Option<LoD>,
points: Vec<PointDraft<VR>>,
},
MultiLineString {
lod: Option<LoD>,
linestrings: Vec<LineStringDraft<VR>>,
},
MultiSurface {
lod: Option<LoD>,
surfaces: Vec<SurfaceDraft<VR, SS>>,
},
CompositeSurface {
lod: Option<LoD>,
surfaces: Vec<SurfaceDraft<VR, SS>>,
},
Solid {
lod: Option<LoD>,
solid: SolidDraft<VR, SS>,
},
MultiSolid {
lod: Option<LoD>,
solids: Vec<SolidDraft<VR, SS>>,
},
CompositeSolid {
lod: Option<LoD>,
solids: Vec<SolidDraft<VR, SS>>,
},
GeometryInstance {
template: GeometryTemplateHandle,
reference_point: VertexDraft<VR>,
transformation: AffineTransform3D,
},
}
impl<VR: VertexRef, SS: StringStorage> GeometryDraft<VR, SS> {
pub fn multi_point<I>(lod: Option<LoD>, points: I) -> Self
where
I: IntoIterator<Item = PointDraft<VR>>,
{
Self::MultiPoint {
lod,
points: points.into_iter().collect(),
}
}
pub fn multi_line_string<I>(lod: Option<LoD>, linestrings: I) -> Self
where
I: IntoIterator<Item = LineStringDraft<VR>>,
{
Self::MultiLineString {
lod,
linestrings: linestrings.into_iter().collect(),
}
}
pub fn multi_surface<I>(lod: Option<LoD>, surfaces: I) -> Self
where
I: IntoIterator<Item = SurfaceDraft<VR, SS>>,
{
Self::MultiSurface {
lod,
surfaces: surfaces.into_iter().collect(),
}
}
pub fn composite_surface<I>(lod: Option<LoD>, surfaces: I) -> Self
where
I: IntoIterator<Item = SurfaceDraft<VR, SS>>,
{
Self::CompositeSurface {
lod,
surfaces: surfaces.into_iter().collect(),
}
}
pub fn solid<I>(lod: Option<LoD>, outer: ShellDraft<VR, SS>, inners: I) -> Self
where
I: IntoIterator<Item = ShellDraft<VR, SS>>,
{
Self::Solid {
lod,
solid: SolidDraft::new(outer, inners),
}
}
pub fn multi_solid<I>(lod: Option<LoD>, solids: I) -> Self
where
I: IntoIterator<Item = SolidDraft<VR, SS>>,
{
Self::MultiSolid {
lod,
solids: solids.into_iter().collect(),
}
}
pub fn composite_solid<I>(lod: Option<LoD>, solids: I) -> Self
where
I: IntoIterator<Item = SolidDraft<VR, SS>>,
{
Self::CompositeSolid {
lod,
solids: solids.into_iter().collect(),
}
}
pub fn instance<T>(
template: GeometryTemplateHandle,
reference_point: T,
transformation: AffineTransform3D,
) -> Self
where
T: Into<VertexDraft<VR>>,
{
Self::GeometryInstance {
template,
reference_point: reference_point.into(),
transformation,
}
}
#[must_use]
pub fn type_geometry(&self) -> GeometryType {
match self {
Self::MultiPoint { .. } => GeometryType::MultiPoint,
Self::MultiLineString { .. } => GeometryType::MultiLineString,
Self::MultiSurface { .. } => GeometryType::MultiSurface,
Self::CompositeSurface { .. } => GeometryType::CompositeSurface,
Self::Solid { .. } => GeometryType::Solid,
Self::MultiSolid { .. } => GeometryType::MultiSolid,
Self::CompositeSolid { .. } => GeometryType::CompositeSolid,
Self::GeometryInstance { .. } => GeometryType::GeometryInstance,
}
}
#[must_use]
pub fn lod(&self) -> Option<LoD> {
match self {
Self::MultiPoint { lod, .. }
| Self::MultiLineString { lod, .. }
| Self::MultiSurface { lod, .. }
| Self::CompositeSurface { lod, .. }
| Self::Solid { lod, .. }
| Self::MultiSolid { lod, .. }
| Self::CompositeSolid { lod, .. } => *lod,
Self::GeometryInstance { .. } => None,
}
}
pub fn insert_into(self, model: &mut CityModel<VR, SS>) -> Result<GeometryHandle> {
let geometry = self.build_stored(model, DraftInsertMode::Regular)?;
model.add_geometry_unchecked(geometry)
}
pub fn insert_template_into(
self,
model: &mut CityModel<VR, SS>,
) -> Result<GeometryTemplateHandle> {
let geometry = self.build_stored(model, DraftInsertMode::Template)?;
model.add_geometry_template_unchecked(geometry)
}
fn build_stored(
self,
model: &mut CityModel<VR, SS>,
mode: DraftInsertMode,
) -> Result<Geometry<VR, SS>> {
self.validate_draft()?;
let analysis = self.analyze(model, mode)?;
analysis.preflight(model, mode)?;
let mut resolver = DraftResolver::new(model, mode);
let geometry = match self {
Self::GeometryInstance {
template,
reference_point,
transformation,
} => {
let reference_point = resolver.resolve_vertex(&reference_point)?;
Geometry::from_raw_parts(
GeometryType::GeometryInstance,
None,
None,
None,
None,
None,
Some(GeometryInstanceData::new(
template.to_raw(),
reference_point,
transformation,
)),
)
}
draft => draft.build_regular_geometry(&mut resolver)?,
};
Ok(geometry)
}
fn build_regular_geometry(
self,
resolver: &mut DraftResolver<'_, VR, SS>,
) -> Result<Geometry<VR, SS>> {
let type_geometry = self.type_geometry();
let lod = self.lod();
let mut boundary = Boundary::new();
let mut semantics = None;
let mut materials = Vec::new();
let mut textures = Vec::new();
match self {
Self::MultiPoint { points, .. } => {
build_multi_point_geometry(resolver, &mut boundary, &mut semantics, &points)?;
}
Self::MultiLineString { linestrings, .. } => build_multi_linestring_geometry(
resolver,
&mut boundary,
&mut semantics,
&linestrings,
)?,
Self::MultiSurface { surfaces, .. } | Self::CompositeSurface { surfaces, .. } => {
build_surface_geometry(
resolver,
&mut boundary,
&mut semantics,
&mut materials,
&mut textures,
&surfaces,
)?;
}
Self::Solid { solid, .. } => {
build_solid_geometry(
resolver,
&mut boundary,
&mut semantics,
&mut materials,
&mut textures,
&solid,
)?;
}
Self::MultiSolid { solids, .. } | Self::CompositeSolid { solids, .. } => {
build_multi_solid_geometry(
resolver,
&mut boundary,
&mut semantics,
&mut materials,
&mut textures,
&solids,
)?;
}
Self::GeometryInstance { .. } => unreachable!("handled separately"),
}
Ok(Geometry::from_raw_parts(
type_geometry,
lod,
Some(boundary),
semantics,
if materials.is_empty() {
None
} else {
Some(materials)
},
if textures.is_empty() {
None
} else {
Some(textures)
},
None,
))
}
fn validate_draft(&self) -> Result<()> {
match self {
Self::MultiPoint { points, .. } => {
if points.is_empty() {
return Err(Error::InvalidGeometry(
"MultiPoint draft requires at least one point".to_string(),
));
}
}
Self::MultiLineString { linestrings, .. } => {
if linestrings.is_empty() {
return Err(Error::InvalidGeometry(
"MultiLineString draft requires at least one linestring".to_string(),
));
}
for linestring in linestrings {
validate_non_empty_vertices("linestring", &linestring.vertices)?;
}
}
Self::MultiSurface { surfaces, .. } | Self::CompositeSurface { surfaces, .. } => {
validate_surface_list("surface geometry", surfaces)?;
}
Self::Solid { solid, .. } => {
validate_shell("solid outer shell", &solid.outer)?;
for shell in &solid.inners {
validate_shell("solid inner shell", shell)?;
}
}
Self::MultiSolid { solids, .. } | Self::CompositeSolid { solids, .. } => {
if solids.is_empty() {
return Err(Error::InvalidGeometry(
"multi-solid draft requires at least one solid".to_string(),
));
}
for solid in solids {
validate_shell("solid outer shell", &solid.outer)?;
for shell in &solid.inners {
validate_shell("solid inner shell", shell)?;
}
}
}
Self::GeometryInstance { .. } => {}
}
Ok(())
}
fn analyze(&self, model: &CityModel<VR, SS>, mode: DraftInsertMode) -> Result<DraftAnalysis> {
let mut analysis = DraftAnalysis::default();
match self {
Self::MultiPoint { points, .. } => {
for point in points {
analysis.record_vertex(model, mode, &point.vertex)?;
DraftAnalysis::require_semantic(model, point.semantic)?;
analysis.vertices += 1;
}
}
Self::MultiLineString { linestrings, .. } => {
for linestring in linestrings {
analysis.rings += 1;
DraftAnalysis::require_semantic(model, linestring.semantic)?;
for vertex in &linestring.vertices {
analysis.record_vertex(model, mode, vertex)?;
analysis.vertices += 1;
}
}
}
Self::MultiSurface { surfaces, .. } | Self::CompositeSurface { surfaces, .. } => {
for surface in surfaces {
analyze_surface(model, mode, surface, &mut analysis)?;
}
}
Self::Solid { solid, .. } => {
for shell in std::iter::once(&solid.outer).chain(solid.inners.iter()) {
analysis.shells += 1;
for surface in &shell.surfaces {
analyze_surface(model, mode, surface, &mut analysis)?;
}
}
}
Self::MultiSolid { solids, .. } | Self::CompositeSolid { solids, .. } => {
for solid in solids {
analysis.solids += 1;
for shell in std::iter::once(&solid.outer).chain(solid.inners.iter()) {
analysis.shells += 1;
for surface in &shell.surfaces {
analyze_surface(model, mode, surface, &mut analysis)?;
}
}
}
}
Self::GeometryInstance {
template,
reference_point,
..
} => {
if mode == DraftInsertMode::Template {
return Err(Error::InvalidGeometry(
"GeometryInstance cannot be inserted into the template geometry pool"
.to_string(),
));
}
if model.get_geometry_template(*template).is_none() {
return Err(Error::InvalidGeometry(format!(
"GeometryInstance references missing template {}",
template.to_raw()
)));
}
analysis.record_vertex(model, DraftInsertMode::Regular, reference_point)?;
}
}
VertexIndex::<VR>::try_from(analysis.vertices)?;
VertexIndex::<VR>::try_from(analysis.rings)?;
VertexIndex::<VR>::try_from(analysis.surfaces)?;
VertexIndex::<VR>::try_from(analysis.shells)?;
VertexIndex::<VR>::try_from(analysis.solids)?;
Ok(analysis)
}
}
fn validate_surface_list<VR: VertexRef, SS: StringStorage>(
label: &str,
surfaces: &[SurfaceDraft<VR, SS>],
) -> Result<()> {
if surfaces.is_empty() {
return Err(Error::InvalidGeometry(format!(
"{label} requires at least one surface"
)));
}
for surface in surfaces {
validate_surface(surface)?;
}
Ok(())
}
fn validate_surface<VR: VertexRef, SS: StringStorage>(
surface: &SurfaceDraft<VR, SS>,
) -> Result<()> {
validate_ring(&surface.outer)?;
for ring in &surface.inners {
validate_ring(ring)?;
}
validate_unique_surface_themes(surface)?;
Ok(())
}
fn validate_shell<VR: VertexRef, SS: StringStorage>(
label: &str,
shell: &ShellDraft<VR, SS>,
) -> Result<()> {
if shell.surfaces.is_empty() {
return Err(Error::InvalidGeometry(format!(
"{label} requires at least one surface"
)));
}
for surface in &shell.surfaces {
validate_surface(surface)?;
}
Ok(())
}
fn validate_ring<VR: VertexRef, SS: StringStorage>(ring: &RingDraft<VR, SS>) -> Result<()> {
validate_non_empty_vertices("ring", &ring.vertices)?;
validate_unique_ring_themes(ring)?;
for texture in &ring.textures {
if texture.uvs.len() != ring.vertices.len() {
return Err(Error::InvalidGeometry(format!(
"ring texture theme '{}' has {} UVs for {} vertices",
texture.theme,
texture.uvs.len(),
ring.vertices.len()
)));
}
}
Ok(())
}
fn validate_non_empty_vertices<VR: VertexRef>(
label: &str,
vertices: &[VertexDraft<VR>],
) -> Result<()> {
if vertices.is_empty() {
return Err(Error::InvalidGeometry(format!(
"{label} requires at least one vertex"
)));
}
Ok(())
}
fn validate_unique_ring_themes<VR: VertexRef, SS: StringStorage>(
ring: &RingDraft<VR, SS>,
) -> Result<()> {
let mut seen = HashSet::<&str>::new();
for texture in &ring.textures {
let theme = texture.theme.as_ref();
if !seen.insert(theme) {
return Err(Error::InvalidGeometry(format!(
"ring texture theme '{theme}' is assigned more than once"
)));
}
}
Ok(())
}
fn validate_unique_surface_themes<VR: VertexRef, SS: StringStorage>(
surface: &SurfaceDraft<VR, SS>,
) -> Result<()> {
let mut seen = HashSet::<&str>::new();
for (theme, _) in &surface.materials {
let theme_ref = theme.as_ref();
if !seen.insert(theme_ref) {
return Err(Error::InvalidGeometry(format!(
"surface material theme '{theme_ref}' is assigned more than once"
)));
}
}
Ok(())
}
#[derive(Default)]
struct DraftAnalysis {
vertices: usize,
rings: usize,
surfaces: usize,
shells: usize,
solids: usize,
new_vertices: usize,
new_uvs: usize,
seen_new_vertices: HashSet<RealWorldCoordinateKey>,
seen_new_uvs: HashSet<UvCoordinateKey>,
}
impl DraftAnalysis {
fn preflight<VR: VertexRef, SS: StringStorage>(
&self,
model: &mut CityModel<VR, SS>,
mode: DraftInsertMode,
) -> Result<()> {
model.reserve_draft_insert(mode, self.new_vertices, self.new_uvs)
}
fn record_vertex<VR: VertexRef, SS: StringStorage>(
&mut self,
model: &CityModel<VR, SS>,
mode: DraftInsertMode,
vertex: &VertexDraft<VR>,
) -> Result<()> {
match vertex {
VertexDraft::Existing(index) => {
let exists = match mode {
DraftInsertMode::Regular => model.get_vertex(*index).is_some(),
DraftInsertMode::Template => model.get_template_vertex(*index).is_some(),
};
if !exists {
let pool = match mode {
DraftInsertMode::Regular => "regular",
DraftInsertMode::Template => "template",
};
return Err(Error::InvalidGeometry(format!(
"draft references missing {pool} vertex {index}"
)));
}
}
VertexDraft::New(coord) => {
if self.seen_new_vertices.insert((*coord).into()) {
self.new_vertices += 1;
}
}
}
Ok(())
}
fn record_uv<VR: VertexRef, SS: StringStorage>(
&mut self,
model: &CityModel<VR, SS>,
uv: &UvDraft<VR>,
) -> Result<()> {
match uv {
UvDraft::Existing(index) => {
if model.get_uv_coordinate(*index).is_none() {
return Err(Error::InvalidGeometry(format!(
"draft references missing UV {index}"
)));
}
}
UvDraft::New(coord) => {
if self.seen_new_uvs.insert(coord.into()) {
self.new_uvs += 1;
}
}
}
Ok(())
}
fn require_semantic<VR: VertexRef, SS: StringStorage>(
model: &CityModel<VR, SS>,
semantic: Option<SemanticHandle>,
) -> Result<()> {
if let Some(handle) = semantic
&& model.get_semantic(handle).is_none()
{
return Err(Error::InvalidGeometry(format!(
"draft references missing semantic {}",
handle.to_raw()
)));
}
Ok(())
}
fn require_material<VR: VertexRef, SS: StringStorage>(
model: &CityModel<VR, SS>,
material: MaterialHandle,
) -> Result<()> {
if model.get_material(material).is_none() {
return Err(Error::InvalidGeometry(format!(
"draft references missing material {}",
material.to_raw()
)));
}
Ok(())
}
fn require_texture<VR: VertexRef, SS: StringStorage>(
model: &CityModel<VR, SS>,
texture: TextureHandle,
) -> Result<()> {
if model.get_texture(texture).is_none() {
return Err(Error::InvalidGeometry(format!(
"draft references missing texture {}",
texture.to_raw()
)));
}
Ok(())
}
}
fn analyze_surface<VR: VertexRef, SS: StringStorage>(
model: &CityModel<VR, SS>,
mode: DraftInsertMode,
surface: &SurfaceDraft<VR, SS>,
analysis: &mut DraftAnalysis,
) -> Result<()> {
analysis.surfaces += 1;
DraftAnalysis::require_semantic(model, surface.semantic)?;
for (_, material) in &surface.materials {
DraftAnalysis::require_material(model, *material)?;
}
analyze_ring(model, mode, &surface.outer, analysis)?;
for ring in &surface.inners {
analyze_ring(model, mode, ring, analysis)?;
}
Ok(())
}
fn analyze_ring<VR: VertexRef, SS: StringStorage>(
model: &CityModel<VR, SS>,
mode: DraftInsertMode,
ring: &RingDraft<VR, SS>,
analysis: &mut DraftAnalysis,
) -> Result<()> {
analysis.rings += 1;
for vertex in &ring.vertices {
analysis.record_vertex(model, mode, vertex)?;
analysis.vertices += 1;
}
for texture in &ring.textures {
DraftAnalysis::require_texture(model, texture.texture)?;
for uv in &texture.uvs {
analysis.record_uv(model, uv)?;
}
}
Ok(())
}
fn build_multi_point_geometry<VR: VertexRef, SS: StringStorage>(
resolver: &mut DraftResolver<'_, VR, SS>,
boundary: &mut Boundary<VR>,
semantics: &mut Option<SemanticOrMaterialMap<VR, ResourceId32>>,
points: &[PointDraft<VR>],
) -> Result<()> {
for (point_index, point) in points.iter().enumerate() {
let vertex = resolver.resolve_vertex(&point.vertex)?;
boundary.vertices.push(vertex);
push_dense_assignment(
semantics,
point_index,
point.semantic.map(SemanticHandle::to_raw),
DenseBucket::Points,
);
}
Ok(())
}
fn build_multi_linestring_geometry<VR: VertexRef, SS: StringStorage>(
resolver: &mut DraftResolver<'_, VR, SS>,
boundary: &mut Boundary<VR>,
semantics: &mut Option<SemanticOrMaterialMap<VR, ResourceId32>>,
linestrings: &[LineStringDraft<VR>],
) -> Result<()> {
for (linestring_index, linestring) in linestrings.iter().enumerate() {
boundary
.rings
.push(VertexIndex::try_from(boundary.vertices.len())?);
for vertex in &linestring.vertices {
boundary.vertices.push(resolver.resolve_vertex(vertex)?);
}
push_dense_assignment(
semantics,
linestring_index,
linestring.semantic.map(SemanticHandle::to_raw),
DenseBucket::Linestrings,
);
}
Ok(())
}
fn build_surface_geometry<VR: VertexRef, SS: StringStorage>(
resolver: &mut DraftResolver<'_, VR, SS>,
boundary: &mut Boundary<VR>,
semantics: &mut Option<SemanticOrMaterialMap<VR, ResourceId32>>,
materials: &mut Vec<(ThemeName<SS>, SemanticOrMaterialMap<VR, ResourceId32>)>,
textures: &mut Vec<(ThemeName<SS>, TextureMapCore<VR, ResourceId32>)>,
surfaces: &[SurfaceDraft<VR, SS>],
) -> Result<()> {
let mut surface_index = 0;
for surface in surfaces {
flatten_surface(
resolver,
boundary,
semantics,
materials,
textures,
&mut surface_index,
surface,
)?;
}
Ok(())
}
fn build_solid_geometry<VR: VertexRef, SS: StringStorage>(
resolver: &mut DraftResolver<'_, VR, SS>,
boundary: &mut Boundary<VR>,
semantics: &mut Option<SemanticOrMaterialMap<VR, ResourceId32>>,
materials: &mut Vec<(ThemeName<SS>, SemanticOrMaterialMap<VR, ResourceId32>)>,
textures: &mut Vec<(ThemeName<SS>, TextureMapCore<VR, ResourceId32>)>,
solid: &SolidDraft<VR, SS>,
) -> Result<()> {
let mut surface_index = 0;
flatten_shell(
resolver,
boundary,
semantics,
materials,
textures,
&mut surface_index,
&solid.outer,
)?;
for shell in &solid.inners {
flatten_shell(
resolver,
boundary,
semantics,
materials,
textures,
&mut surface_index,
shell,
)?;
}
Ok(())
}
fn build_multi_solid_geometry<VR: VertexRef, SS: StringStorage>(
resolver: &mut DraftResolver<'_, VR, SS>,
boundary: &mut Boundary<VR>,
semantics: &mut Option<SemanticOrMaterialMap<VR, ResourceId32>>,
materials: &mut Vec<(ThemeName<SS>, SemanticOrMaterialMap<VR, ResourceId32>)>,
textures: &mut Vec<(ThemeName<SS>, TextureMapCore<VR, ResourceId32>)>,
solids: &[SolidDraft<VR, SS>],
) -> Result<()> {
let mut surface_index = 0;
for solid in solids {
boundary
.solids
.push(VertexIndex::try_from(boundary.shells.len())?);
flatten_shell(
resolver,
boundary,
semantics,
materials,
textures,
&mut surface_index,
&solid.outer,
)?;
for shell in &solid.inners {
flatten_shell(
resolver,
boundary,
semantics,
materials,
textures,
&mut surface_index,
shell,
)?;
}
}
Ok(())
}
struct DraftResolver<'a, VR: VertexRef, SS: StringStorage> {
model: &'a mut CityModel<VR, SS>,
mode: DraftInsertMode,
new_vertices: HashMap<RealWorldCoordinateKey, VertexIndex<VR>>,
new_uvs: HashMap<UvCoordinateKey, VertexIndex<VR>>,
}
impl<'a, VR: VertexRef, SS: StringStorage> DraftResolver<'a, VR, SS> {
fn new(model: &'a mut CityModel<VR, SS>, mode: DraftInsertMode) -> Self {
Self {
model,
mode,
new_vertices: HashMap::new(),
new_uvs: HashMap::new(),
}
}
fn resolve_vertex(&mut self, vertex: &VertexDraft<VR>) -> Result<VertexIndex<VR>> {
match vertex {
VertexDraft::Existing(index) => Ok(*index),
VertexDraft::New(coord) => {
let key: RealWorldCoordinateKey = (*coord).into();
if let Some(index) = self.new_vertices.get(&key) {
return Ok(*index);
}
let index = match self.mode {
DraftInsertMode::Regular => self.model.add_vertex(*coord)?,
DraftInsertMode::Template => self.model.add_template_vertex(*coord)?,
};
self.new_vertices.insert(key, index);
Ok(index)
}
}
}
fn resolve_uv(&mut self, uv: &UvDraft<VR>) -> Result<VertexIndex<VR>> {
match uv {
UvDraft::Existing(index) => Ok(*index),
UvDraft::New(coord) => {
let key: UvCoordinateKey = coord.into();
if let Some(index) = self.new_uvs.get(&key) {
return Ok(*index);
}
let index = self.model.add_uv_coordinate(coord.clone())?;
self.new_uvs.insert(key, index);
Ok(index)
}
}
}
}
#[derive(Clone, Copy)]
enum DenseBucket {
Points,
Linestrings,
Surfaces,
}
fn push_dense_assignment<VR: VertexRef>(
map: &mut Option<SemanticOrMaterialMap<VR, ResourceId32>>,
current_index: usize,
assignment: Option<ResourceId32>,
bucket: DenseBucket,
) {
if map.is_none() && assignment.is_none() {
return;
}
let map = map.get_or_insert_with(|| {
let mut map = SemanticOrMaterialMap::new();
for _ in 0..current_index {
match bucket {
DenseBucket::Points => map.add_point(None),
DenseBucket::Linestrings => map.add_linestring(None),
DenseBucket::Surfaces => map.add_surface(None),
}
}
map
});
match bucket {
DenseBucket::Points => map.add_point(assignment),
DenseBucket::Linestrings => map.add_linestring(assignment),
DenseBucket::Surfaces => map.add_surface(assignment),
}
}
fn flatten_shell<VR: VertexRef, SS: StringStorage>(
resolver: &mut DraftResolver<'_, VR, SS>,
boundary: &mut Boundary<VR>,
semantics: &mut Option<SemanticOrMaterialMap<VR, ResourceId32>>,
materials: &mut Vec<(ThemeName<SS>, SemanticOrMaterialMap<VR, ResourceId32>)>,
textures: &mut Vec<(ThemeName<SS>, TextureMapCore<VR, ResourceId32>)>,
surface_index: &mut usize,
shell: &ShellDraft<VR, SS>,
) -> Result<()> {
boundary
.shells
.push(VertexIndex::try_from(boundary.surfaces.len())?);
for surface in &shell.surfaces {
flatten_surface(
resolver,
boundary,
semantics,
materials,
textures,
surface_index,
surface,
)?;
}
Ok(())
}
fn flatten_surface<VR: VertexRef, SS: StringStorage>(
resolver: &mut DraftResolver<'_, VR, SS>,
boundary: &mut Boundary<VR>,
semantics: &mut Option<SemanticOrMaterialMap<VR, ResourceId32>>,
materials: &mut Vec<(ThemeName<SS>, SemanticOrMaterialMap<VR, ResourceId32>)>,
textures: &mut Vec<(ThemeName<SS>, TextureMapCore<VR, ResourceId32>)>,
surface_index: &mut usize,
surface: &SurfaceDraft<VR, SS>,
) -> Result<()> {
let current_surface = *surface_index;
boundary
.surfaces
.push(VertexIndex::try_from(boundary.rings.len())?);
push_dense_assignment(
semantics,
current_surface,
surface.semantic.map(SemanticHandle::to_raw),
DenseBucket::Surfaces,
);
push_surface_materials::<VR, SS>(materials, current_surface, &surface.materials);
flatten_ring(resolver, boundary, textures, &surface.outer)?;
for ring in &surface.inners {
flatten_ring(resolver, boundary, textures, ring)?;
}
*surface_index += 1;
Ok(())
}
fn push_surface_materials<VR: VertexRef, SS: StringStorage>(
materials: &mut Vec<(ThemeName<SS>, SemanticOrMaterialMap<VR, ResourceId32>)>,
current_surface: usize,
assignments: &[(ThemeName<SS>, MaterialHandle)],
) {
for (_, map) in materials.iter_mut() {
map.add_surface(None);
}
for (theme, material) in assignments {
if let Some((_, map)) = materials.iter_mut().find(|(name, _)| name == theme) {
if let Some(slot) = map.surfaces.last_mut() {
*slot = Some(material.to_raw());
}
continue;
}
let mut map = SemanticOrMaterialMap::new();
for _ in 0..current_surface {
map.add_surface(None);
}
map.add_surface(Some(material.to_raw()));
materials.push((theme.clone(), map));
}
}
fn flatten_ring<VR: VertexRef, SS: StringStorage>(
resolver: &mut DraftResolver<'_, VR, SS>,
boundary: &mut Boundary<VR>,
textures: &mut Vec<(ThemeName<SS>, TextureMapCore<VR, ResourceId32>)>,
ring: &RingDraft<VR, SS>,
) -> Result<()> {
let ring_index = boundary.rings.len();
let ring_start = VertexIndex::try_from(boundary.vertices.len())?;
let ring_vertex_start = boundary.vertices.len();
boundary.rings.push(ring_start);
for vertex in &ring.vertices {
boundary.vertices.push(resolver.resolve_vertex(vertex)?);
}
for (_, texture_map) in textures.iter_mut() {
texture_map.add_ring(ring_start);
texture_map.add_ring_texture(None);
for _ in &ring.vertices {
texture_map.add_vertex(None);
}
}
for texture in &ring.textures {
let resolved_uvs: Result<Vec<_>> = texture
.uvs
.iter()
.map(|uv| resolver.resolve_uv(uv))
.collect();
let resolved_uvs = resolved_uvs?;
if let Some((_, texture_map)) = textures
.iter_mut()
.find(|(theme, _)| theme == &texture.theme)
{
let ring_slot = texture_map.ring_textures_mut()[ring_index..=ring_index]
.first_mut()
.expect("ring slot exists");
*ring_slot = Some(texture.texture.to_raw());
let vertex_slice = &mut texture_map.vertices_mut()
[ring_vertex_start..ring_vertex_start + resolved_uvs.len()];
for (slot, uv) in vertex_slice.iter_mut().zip(resolved_uvs) {
*slot = Some(uv);
}
continue;
}
let mut texture_map = TextureMapCore::default();
for prior_vertex in 0..ring_vertex_start {
let _ = prior_vertex;
texture_map.add_vertex(None);
}
for &prior_ring_start in &boundary.rings()[..ring_index] {
texture_map.add_ring(prior_ring_start);
texture_map.add_ring_texture(None);
}
texture_map.add_ring(ring_start);
texture_map.add_ring_texture(Some(texture.texture.to_raw()));
for uv in resolved_uvs {
texture_map.add_vertex(Some(uv));
}
textures.push((texture.theme.clone(), texture_map));
}
Ok(())
}