Skip to content

Commit

Permalink
3dtiles: ジオメトリにUV座標ももたせる (#285)
Browse files Browse the repository at this point in the history
3D Tiles
の処理の流れにおいて、ポリゴンの次元を5次元に拡張して、位置座標といっしょにテクスチャのUV座標をもたせるようにするだけの改修です。

(いまのところUVの情報は使わずに途中で捨てています)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit


- **新機能**
-
ポリゴンをマルチポリゴン構造に追加する際に、ポリゴンのコピーを回避して効率を向上させるために、`MultiPolygon`実装内の`push`メソッドがポリゴンの参照を取るように変更されました。
- **バグ修正**
    - 特定のコンテキスト内で最後のUVが最初のUVと同じである必要がある場合のエラーハンドリングを追加しました。
- **リファクタ**
    - 複数の関数呼び出しで追加の引数を含むように変更しました。
- `geometry_slicing_stage`と`tile_writing_stage`でのデータ変換とインデックス処理を調整しました。
- **スタイル**
    - 複数のインポート文と変数名を更新しました。

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
  • Loading branch information
ciscorn authored Feb 19, 2024
1 parent f3e2bb0 commit e73b534
Show file tree
Hide file tree
Showing 15 changed files with 292 additions and 146 deletions.
4 changes: 1 addition & 3 deletions nusamai-citygml/src/geometry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,15 +100,13 @@ impl GeometryCollector {
&mut self,
iter: impl IntoIterator<Item = [f64; 3]>,
ring_id: Option<LocalId>,
) -> usize {
) {
self.ring_ids.push(ring_id);
self.multipolygon.add_exterior(iter.into_iter().map(|v| {
let vbits = [v[0].to_bits(), v[1].to_bits(), v[2].to_bits()];
let (index, _) = self.vertices.insert_full(vbits);
[index as u32]
}));

self.multipolygon.len() - 1
}

pub fn add_interior_ring(
Expand Down
6 changes: 6 additions & 0 deletions nusamai-citygml/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -875,6 +875,7 @@ impl<'b, R: BufRead> SubTreeReader<'_, 'b, R> {
.fp_buf
.chunks_exact(3)
.map(|c| [c[0], c[1], c[2]]);

if is_exterior {
// add a new polygon
self.state
Expand Down Expand Up @@ -1040,6 +1041,11 @@ impl<'b, R: BufRead> SubTreeReader<'_, 'b, R> {
{
self.state.fp_buf.pop();
self.state.fp_buf.pop();
} else {
return Err(ParseError::InvalidValue(format!(
"The last UV coord must be the same as the first: {:?}",
self.state.fp_buf
)));
}
}

Expand Down
6 changes: 6 additions & 0 deletions nusamai-citygml/src/values.rs
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,12 @@ impl Color {
}
}

impl From<Color> for [f32; 4] {
fn from(c: Color) -> [f32; 4] {
[c.r as f32, c.g as f32, c.b as f32, 1.]
}
}

impl std::hash::Hash for Color {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.r.to_bits().hash(state);
Expand Down
8 changes: 4 additions & 4 deletions nusamai-geometry/src/compact/multi_polygon.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ impl<'a, const D: usize, T: CoordNum> MultiPolygon<'a, D, T> {
}

// Adds a polygon to the multipolygon.
pub fn push(&mut self, poly: Polygon<D, T>) {
pub fn push(&mut self, poly: &Polygon<D, T>) {
self.add_exterior(&poly.exterior());
for hole in poly.interiors() {
self.add_interior(&hole);
Expand Down Expand Up @@ -296,21 +296,21 @@ mod tests {
poly1.add_ring([[0., 0.], [5., 0.], [5., 5.], [0., 5.]]); // exterior
poly1.add_ring([[1., 1.], [2., 1.], [2., 2.], [1., 2.]]); // interior
poly1.add_ring([[3., 3.], [4., 3.], [4., 4.], [3., 4.]]); // interior
mpoly.push(poly1);
mpoly.push(&poly1);
assert!(!mpoly.is_empty());
assert_eq!(mpoly.len(), 1);

// 2nd polygon
let mut poly2 = Polygon2::new();
poly2.add_ring([[4., 0.], [7., 0.], [7., 3.], [4., 3.]]); // exterior
poly2.add_ring([[5., 1.], [6., 1.], [6., 2.], [5., 2.]]); // interior
mpoly.push(poly2);
mpoly.push(&poly2);
assert_eq!(mpoly.len(), 2);

// 3rd polygon
let mut poly3 = Polygon2::new();
poly3.add_ring([[4., 0.], [7., 0.], [7., 3.], [4., 3.]]); // exterior
mpoly.push(poly3);
mpoly.push(&poly3);
assert_eq!(mpoly.len(), 3);

for (i, poly) in mpoly.iter().enumerate() {
Expand Down
1 change: 1 addition & 0 deletions nusamai-plateau/src/appearance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ pub struct Material {
pub specular_color: Color,
pub ambient_intensity: f64,
// TOOD: other parameters
// Note: Adjust the Hash implementation if you add a new field
}

impl From<X3DMaterial> for Material {
Expand Down
2 changes: 1 addition & 1 deletion nusamai-projection/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ version = "0.1.0"
edition = "2021"

[dependencies]
japan-geoid = { git = "https://github.com/MIERUNE/japan-geoid" }
japan-geoid = "0.4.0"
thiserror = "1.0.57"
19 changes: 19 additions & 0 deletions nusamai/src/sink/cesiumtiles/material.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//! Material mangement

use std::hash::Hash;

use serde::{Deserialize, Serialize};

#[derive(Serialize, Clone, PartialEq, Deserialize)]
pub struct Material {
pub base_color: [f32; 4],
// NOTE: Adjust the hash implementation if you add more fields
}

impl Eq for Material {}

impl Hash for Material {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.base_color.iter().for_each(|c| c.to_bits().hash(state));
}
}
100 changes: 47 additions & 53 deletions nusamai/src/sink/cesiumtiles/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@

use std::fs;
mod gltf;
mod material;
mod slice;
mod sort;
mod tiling;

use indexmap::IndexSet;
use itertools::Itertools;
use std::io::BufWriter;
use std::path::{Path, PathBuf};
use std::sync::{mpsc, Arc, Mutex};
Expand All @@ -14,23 +17,20 @@ use ahash::RandomState;
use earcut_rs::utils3d::project3d_to_2d;
use earcut_rs::Earcut;
use ext_sort::{buffer::mem::MemoryLimitedBufferBuilder, ExternalSorter, ExternalSorterBuilder};
use indexmap::IndexSet;
use itertools::Itertools;
use nusamai_mvt::TileZXY;
use nusamai_projection::cartesian::geographic_to_geocentric;
use rayon::prelude::*;
use serde::{Deserialize, Serialize};

use nusamai_citygml::schema::Schema;
use nusamai_geometry::MultiPolygon;
use nusamai_mvt::tileid::TileIdMethod;

use crate::parameters::*;
use crate::pipeline::{Feedback, PipelineError, Receiver, Result};
use crate::sink::cesiumtiles::gltf::write_gltf_glb;
use crate::sink::{DataSink, DataSinkProvider, SinkInfo};
use crate::{get_parameter_value, transformer};
use slice::slice_cityobj_geoms;
use slice::{slice_to_tiles, SlicedFeature};
use sort::BincodeExternalChunk;

use tiling::{TileSpec, TileTree};
Expand Down Expand Up @@ -83,12 +83,6 @@ struct SerializedSlicedFeature {
body: Vec<u8>,
}

#[derive(Serialize, Deserialize)]
struct SlicedFeature<'a> {
geometry: MultiPolygon<'a, 3>,
properties: nusamai_citygml::object::Value,
}

impl DataSink for CesiumTilesSink {
fn make_transform_requirements(&self) -> transformer::Requirements {
transformer::Requirements {
Expand Down Expand Up @@ -158,18 +152,14 @@ fn geometry_slicing_stage(
upstream.into_iter().par_bridge().try_for_each(|parcel| {
feedback.ensure_not_canceled()?;

slice_cityobj_geoms(&parcel.entity, 7, 17, |(z, x, y), mpoly| {
let feature = SlicedFeature {
geometry: mpoly,
properties: parcel.entity.root.clone(),
};
// TODO: zoom level from parameters
slice_to_tiles(&parcel.entity, 7, 17, |(z, x, y), feature| {
let bytes = bincode::serialize(&feature).unwrap();
let sfeat = SerializedSlicedFeature {
let serialized_feature = SerializedSlicedFeature {
tile_id: tile_id_conv.zxy_to_id(z, x, y),
body: bytes,
};

if sender_sliced.send(sfeat).is_err() {
if sender_sliced.send(serialized_feature).is_err() {
return Err(PipelineError::Canceled);
};
Ok(())
Expand Down Expand Up @@ -224,7 +214,7 @@ fn tile_writing_stage(
receiver_sorted
.into_iter()
.par_bridge()
.try_for_each(|(tile_id, sfeats)| {
.try_for_each(|(tile_id, serialized_feats)| {
feedback.ensure_not_canceled()?;

let mut tilespec = TileSpec {
Expand All @@ -238,43 +228,51 @@ fn tile_writing_stage(
};

let mut earcutter = Earcut::new();
let mut buf3d: Vec<f64> = Vec::new();
let mut buf2d: Vec<f64> = Vec::new();
let mut buf5d: Vec<f64> = Vec::new(); // [x, y, z, u, v]
let mut buf2d: Vec<f64> = Vec::new(); // 2d-projected [x, y]
let mut triangles_buf: Vec<u32> = Vec::new();
let mut triangles = Vec::new();

for ser_feat in sfeats {
let mut feat: SlicedFeature = bincode::deserialize(&ser_feat.body).unwrap();

feat.geometry.transform_inplace(|&[lng, lat, height]| {
tilespec.min_lng = tilespec.min_lng.min(lng);
tilespec.max_lng = tilespec.max_lng.max(lng);
tilespec.min_lat = tilespec.min_lat.min(lat);
tilespec.max_lat = tilespec.max_lat.max(lat);
tilespec.min_height = tilespec.min_height.min(height);
tilespec.max_height = tilespec.max_height.max(height);

let (x, y, z) = geographic_to_geocentric(&ellipsoid, lng, lat, height);
[x, z, -y]
});

for poly in &feat.geometry {
for serialized_feat in serialized_feats {
let mut feature: SlicedFeature = bincode::deserialize(&serialized_feat.body)
.map_err(|err| {
PipelineError::Other(format!(
"Failed to deserialize a sliced feature: {:?}",
err
))
})?;

feature
.polygons
.transform_inplace(|&[lng, lat, height, u, v]| {
tilespec.min_lng = tilespec.min_lng.min(lng);
tilespec.max_lng = tilespec.max_lng.max(lng);
tilespec.min_lat = tilespec.min_lat.min(lat);
tilespec.max_lat = tilespec.max_lat.max(lat);
tilespec.min_height = tilespec.min_height.min(height);
tilespec.max_height = tilespec.max_height.max(height);

let (x, y, z) = geographic_to_geocentric(&ellipsoid, lng, lat, height);
[x, z, -y, u, v]
});

for poly in &feature.polygons {
let num_outer = match poly.hole_indices().first() {
Some(&v) => v as usize,
None => poly.coords().len() / 3,
None => poly.coords().len() / 5,
};

buf3d.clear();
buf3d.extend(poly.coords());
buf5d.clear();
buf5d.extend(poly.coords());

if project3d_to_2d(&buf3d, num_outer, 3, &mut buf2d) {
if project3d_to_2d(&buf5d, num_outer, 5, &mut buf2d) {
// earcut
earcutter.earcut(&buf2d, poly.hole_indices(), 2, &mut triangles_buf);
triangles.extend(triangles_buf.iter().map(|idx| {
[
buf3d[*idx as usize * 3],
buf3d[*idx as usize * 3 + 1],
buf3d[*idx as usize * 3 + 2],
buf5d[*idx as usize * 5],
buf5d[*idx as usize * 5 + 1],
buf5d[*idx as usize * 5 + 2],
]
}));
}
Expand Down Expand Up @@ -342,21 +340,17 @@ fn tile_writing_stage(
let (min_y, max_y) = tiling::y_slice_range(zoom, y);
let xs = tiling::x_step(zoom, y);
let (min_x, max_x) = tiling::x_slice_range(zoom, x as i32, xs);
println!(
"tile: z={}, x={}, y={} (lng: {} -> {}, lat: {} -> {})",
zoom, x, y, min_x, max_x, min_y, max_y
log::info!(
"tile: z={zoom}, x={x}, y={y} (lng: [{min_x} => {max_x}], lat: [{min_y} => {max_y})"
);
println!("{:?} {:?}", vertices.len(), indices.len());

// write to file
let path_glb = output_path.join(Path::new(&format!("{}/{}/{}.glb", zoom, x, y)));
let path_glb = output_path.join(Path::new(&format!("{zoom}/{x}/{y}.glb")));
if let Some(dir) = path_glb.parent() {
if let Err(e) = fs::create_dir_all(dir) {
panic!("Fatal error: {:?}", e); // FIXME
}
fs::create_dir_all(dir)?;
}

let mut file = std::fs::File::create(path_glb).unwrap();
let mut file = std::fs::File::create(path_glb)?;
let mut writer = BufWriter::new(&mut file);
write_gltf_glb(
&mut writer,
Expand Down
Loading

0 comments on commit e73b534

Please sign in to comment.