diff --git a/nusamai/src/sink/cesiumtiles/mod.rs b/nusamai/src/sink/cesiumtiles/mod.rs index f93c345b..c1935536 100644 --- a/nusamai/src/sink/cesiumtiles/mod.rs +++ b/nusamai/src/sink/cesiumtiles/mod.rs @@ -46,20 +46,7 @@ use crate::{ }; use utils::calculate_normal; -const MAX_TEXTURE_PIXELS_PER_METER: f64 = 30.0; - -// WARN: This function has an equivalent in `atlas-packer/src/texture.rs`. -fn uv_to_pixel_coords(uv_coords: &[(f64, f64)], width: u32, height: u32) -> Vec<(u32, u32)> { - uv_coords - .iter() - .map(|(u, v)| { - ( - (u.clamp(0.0, 1.0) * width as f64).min(width as f64 - 1.0) as u32, - ((1.0 - v.clamp(0.0, 1.0)) * height as f64).min(height as f64 - 1.0) as u32, - ) - }) - .collect() -} +use super::texture_resolution::get_texture_downsample_scale_of_polygon; pub struct CesiumTilesSinkProvider {} @@ -544,45 +531,11 @@ fn tile_writing_stage( let texture_uri = base_texture.uri.to_file_path().unwrap(); let texture_size = texture_size_cache.get_or_insert(&texture_uri); - let pixel_coords = - uv_to_pixel_coords(&uv_coords, texture_size.0, texture_size.1); - - let pixel_per_distance = (0..original_vertices.len()) - .map(|i| { - let j = (i + 1) % original_vertices.len(); - let (euc0, txl0) = ( - ( - original_vertices[i].0, - original_vertices[i].1, - original_vertices[i].2, - ), - pixel_coords[i], - ); - let (euc1, txl1) = ( - ( - original_vertices[j].0, - original_vertices[j].1, - original_vertices[j].2, - ), - pixel_coords[j], - ); - let euc_dist = ((euc0.0 - euc1.0).powi(2) - + (euc0.1 - euc1.1).powi(2) - + (euc0.2 - euc1.2).powi(2)) - .sqrt(); - let txl_dist = ((txl0.0 as f64 - txl1.0 as f64).powi(2) - + (txl0.1 as f64 - txl1.1 as f64).powi(2)) - .sqrt(); - txl_dist / euc_dist - }) - .min_by(|a, b| a.total_cmp(b)) - .unwrap_or(1.0); - - let downsample_scale = if limit_texture_resolution.unwrap_or(false) { - 1.0_f64.min(MAX_TEXTURE_PIXELS_PER_METER / pixel_per_distance) - } else { - 1.0 - }; + let downsample_scale = get_texture_downsample_scale_of_polygon( + &original_vertices, + texture_size, + limit_texture_resolution, + ); let factor = apply_downsample_factor(tile_zoom, downsample_scale as f32); let downsample_factor = DownsampleFactor::new(&factor); diff --git a/nusamai/src/sink/gltf/mod.rs b/nusamai/src/sink/gltf/mod.rs index a7931714..cd13c536 100644 --- a/nusamai/src/sink/gltf/mod.rs +++ b/nusamai/src/sink/gltf/mod.rs @@ -36,6 +36,8 @@ use crate::{ transformer::{TransformerConfig, TransformerOption, TransformerRegistry}, }; +use super::texture_resolution::get_texture_downsample_scale_of_polygon; + pub struct GltfSinkProvider {} impl DataSinkProvider for GltfSinkProvider { @@ -71,6 +73,16 @@ impl DataSinkProvider for GltfSinkProvider { }, ); + params.define( + "limit_texture_resolution".into(), + ParameterEntry { + description: "limiting texture resolution".into(), + required: false, + parameter: ParameterType::Boolean(BooleanParameter { value: None }), + label: Some("距離(メートル)あたりのテクスチャの解像度を制限する".into()), + }, + ); + params } @@ -89,11 +101,14 @@ impl DataSinkProvider for GltfSinkProvider { fn create(&self, params: &Parameters) -> Box { let output_path = get_parameter_value!(params, "@output", FileSystemPath); + let limit_texture_resolution = + *get_parameter_value!(params, "limit_texture_resolution", Boolean); let transform_settings = self.available_transformer(); Box::::new(GltfSink { output_path: output_path.as_ref().unwrap().into(), transform_settings, + limit_texture_resolution, }) } } @@ -101,6 +116,7 @@ impl DataSinkProvider for GltfSinkProvider { pub struct GltfSink { output_path: PathBuf, transform_settings: TransformerRegistry, + limit_texture_resolution: Option, } pub struct BoundingVolume { @@ -449,7 +465,14 @@ impl DataSink for GltfSink { let texture_uri = base_texture.uri.to_file_path().unwrap(); let texture_size = texture_size_cache.get_or_insert(&texture_uri); - let downsample_factor = DownsampleFactor::new(&1.0); + + let downsample_scale = get_texture_downsample_scale_of_polygon( + &original_vertices, + texture_size, + self.limit_texture_resolution, + ) as f32; + + let downsample_factor = DownsampleFactor::new(&downsample_scale); let cropped_texture = CroppedTexture::new( &texture_uri, texture_size, diff --git a/nusamai/src/sink/mod.rs b/nusamai/src/sink/mod.rs index 4b1400ef..c71f5603 100644 --- a/nusamai/src/sink/mod.rs +++ b/nusamai/src/sink/mod.rs @@ -13,6 +13,7 @@ pub mod obj; pub mod ply; pub mod serde; pub mod shapefile; +mod texture_resolution; use nusamai_citygml::schema::Schema; use nusamai_projection::crs; diff --git a/nusamai/src/sink/obj/mod.rs b/nusamai/src/sink/obj/mod.rs index 6e8f5b53..e395f171 100644 --- a/nusamai/src/sink/obj/mod.rs +++ b/nusamai/src/sink/obj/mod.rs @@ -43,6 +43,8 @@ use crate::{ transformer::{TransformerConfig, TransformerOption, TransformerRegistry}, }; +use super::texture_resolution::get_texture_downsample_scale_of_polygon; + pub struct ObjSinkProvider {} impl DataSinkProvider for ObjSinkProvider { @@ -88,6 +90,16 @@ impl DataSinkProvider for ObjSinkProvider { }, ); + params.define( + "limit_texture_resolution".into(), + ParameterEntry { + description: "limiting texture resolution".into(), + required: false, + parameter: ParameterType::Boolean(BooleanParameter { value: None }), + label: Some("距離(メートル)あたりのテクスチャの解像度を制限する".into()), + }, + ); + params } @@ -106,6 +118,8 @@ impl DataSinkProvider for ObjSinkProvider { fn create(&self, params: &Parameters) -> Box { let output_path = get_parameter_value!(params, "@output", FileSystemPath); + let limit_texture_resolution = + *get_parameter_value!(params, "limit_texture_resolution", Boolean); let transform_options = self.available_transformer(); let is_split = get_parameter_value!(params, "split", Boolean).unwrap(); @@ -113,6 +127,7 @@ impl DataSinkProvider for ObjSinkProvider { output_path: output_path.as_ref().unwrap().into(), transform_settings: transform_options, obj_options: ObjParams { is_split }, + limit_texture_resolution, }) } } @@ -121,6 +136,7 @@ pub struct ObjSink { output_path: PathBuf, transform_settings: TransformerRegistry, obj_options: ObjParams, + limit_texture_resolution: Option, } struct ObjParams { @@ -482,10 +498,19 @@ impl DataSink for ObjSink { .iter() .map(|(_, _, _, u, v)| (*u, *v)) .collect::>(); - let downsample_factor = DownsampleFactor::new(&1.0); let texture_size = texture_size_cache.get_or_insert(&texture_uri); + let downsample_scale = get_texture_downsample_scale_of_polygon( + &original_vertices, + texture_size, + self.limit_texture_resolution, + ) + as f32; + + let downsample_factor = + DownsampleFactor::new(&downsample_scale); + let texture = CroppedTexture::new( &texture_uri, texture_size, diff --git a/nusamai/src/sink/texture_resolution.rs b/nusamai/src/sink/texture_resolution.rs new file mode 100644 index 00000000..1e988b14 --- /dev/null +++ b/nusamai/src/sink/texture_resolution.rs @@ -0,0 +1,53 @@ +/// Limits the texture resolution based on the distance (meters) between the vertices of a polygon. +const MAX_TEXTURE_PIXELS_PER_METER: f64 = 30.0; + +// WARN: This function has an equivalent in `atlas-packer/src/texture.rs`. +fn uv_to_pixel_coords(uv_coords: &[(f64, f64)], width: u32, height: u32) -> Vec<(u32, u32)> { + uv_coords + .iter() + .map(|(u, v)| { + ( + (u.clamp(0.0, 1.0) * width as f64).min(width as f64 - 1.0) as u32, + ((1.0 - v.clamp(0.0, 1.0)) * height as f64).min(height as f64 - 1.0) as u32, + ) + }) + .collect() +} + +pub fn get_texture_downsample_scale_of_polygon( + vertices: &[(f64, f64, f64, f64, f64)], // (x, y, z, u, v) + texture_size: (u32, u32), + limit_texture_resolution: Option, +) -> f64 { + let uv_coords = vertices.iter().map(|v| (v.3, v.4)).collect::>(); + + let pixel_coords = uv_to_pixel_coords(&uv_coords, texture_size.0, texture_size.1); + + let pixel_per_distance = (0..vertices.len()) + .map(|i| { + let j = (i + 1) % vertices.len(); + let (euc0, txl0) = ( + (vertices[i].0, vertices[i].1, vertices[i].2), + pixel_coords[i], + ); + let (euc1, txl1) = ( + (vertices[j].0, vertices[j].1, vertices[j].2), + pixel_coords[j], + ); + let euc_dist = + ((euc0.0 - euc1.0).powi(2) + (euc0.1 - euc1.1).powi(2) + (euc0.2 - euc1.2).powi(2)) + .sqrt(); + let txl_dist = ((txl0.0 as f64 - txl1.0 as f64).powi(2) + + (txl0.1 as f64 - txl1.1 as f64).powi(2)) + .sqrt(); + txl_dist / euc_dist + }) + .min_by(|a, b| a.total_cmp(b)) + .unwrap_or(1.0); + + if limit_texture_resolution.unwrap_or(false) { + 1.0_f64.min(MAX_TEXTURE_PIXELS_PER_METER / pixel_per_distance) + } else { + 1.0 + } +}