Skip to content

Commit

Permalink
3dtiles: glTFにマテリアル (PBR Material, Texture) を付与する (#308)
Browse files Browse the repository at this point in the history
glTFにマテリアル(PBRの色とテクスチャ)を付与します。課題はたくさん残っていますが、このあたりで一旦マージすべきかと思います。

興味があれば `nusamai/src/sink/cesiumtiles/` 以下の変化を見て頂ければよいかと思います。

それ以外の部分の変更は、特に重要な意味はないです。

既知の問題+今後の課題:
- #309 
- #310 
- #311 (いまは激遅)
  • Loading branch information
ciscorn authored Feb 21, 2024
1 parent 581e856 commit f5d7a43
Show file tree
Hide file tree
Showing 33 changed files with 1,298 additions and 397 deletions.
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,6 @@ authors = ["MIERUNE Inc. <info@mierune.co.jp>"]
inherits = "release"
lto = "fat"
panic = "abort"

[profile.dev.package.zune-image]
opt-level = 3
2 changes: 1 addition & 1 deletion nusamai-citygml/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,4 @@ quick-xml = "0.31"
serde = { version = "1.0", features = ["derive"], optional = true }
serde_json = { version = "1.0.113", features = ["indexmap"], optional = true }
thiserror = "1.0"
url = "2.5.0"
url = { version = "2.5.0", features = ["serde"] }
10 changes: 5 additions & 5 deletions nusamai-citygml/src/object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
use std::borrow::Cow;

use crate::geometry::GeometryRefs;
use crate::values::{Code, Date, Point, URI};
use crate::values::{Code, Date, Point, Uri};
use crate::Measure;
use serde::{Deserialize, Serialize};

Expand Down Expand Up @@ -49,7 +49,7 @@ pub enum Value {
Double(f64),
Measure(Measure),
Boolean(bool),
URI(URI),
Uri(Uri),
Date(Date),
Point(Point),
Array(Vec<Value>),
Expand Down Expand Up @@ -92,7 +92,7 @@ impl Value {
serde_json::Value::Number(serde_json::Number::from_f64(m.value()).unwrap())
}
Boolean(b) => serde_json::Value::Bool(*b),
URI(u) => serde_json::Value::String(u.value().clone()),
Uri(u) => serde_json::Value::String(u.value().to_string()),
Date(d) => serde_json::Value::String(d.to_string()), // ISO 8601
Point(_) => {
// TODO: Handle Point
Expand Down Expand Up @@ -149,8 +149,8 @@ mod tests {
let obj = Value::Boolean(true);
assert_eq!(obj.to_attribute_json(), json!(true));

let obj = Value::URI(URI::new("http://example.com"));
assert_eq!(obj.to_attribute_json(), json!("http://example.com"));
let obj = Value::Uri(Uri::new(url::Url::parse("http://example.com").unwrap()));
assert_eq!(obj.to_attribute_json(), json!("http://example.com/"));

let obj = Value::Date(Date::from_ymd_opt(2020, 1, 1).unwrap());
assert_eq!(obj.to_attribute_json(), json!("2020-01-01"));
Expand Down
10 changes: 5 additions & 5 deletions nusamai-citygml/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ impl<'a> InternalState<'a> {
}

pub struct ParseContext<'a> {
source_uri: Option<Url>,
source_uri: Url,
code_resolver: &'a dyn CodeResolver,
// Mapping a string gml:id to an integer ID, unique in a single document
id_map: indexmap::IndexSet<String, ahash::RandomState>,
Expand All @@ -80,14 +80,14 @@ pub struct ParseContext<'a> {
impl<'a> ParseContext<'a> {
pub fn new(source_uri: Url, code_resolver: &'a dyn CodeResolver) -> Self {
Self {
source_uri: Some(source_uri),
source_uri,
code_resolver,
..Default::default()
}
}

pub fn source_url(&self) -> Option<&Url> {
self.source_uri.as_ref()
pub fn source_url(&self) -> &Url {
&self.source_uri
}

pub fn code_resolver(&self) -> &dyn CodeResolver {
Expand All @@ -103,7 +103,7 @@ impl<'a> ParseContext<'a> {
impl<'a> Default for ParseContext<'a> {
fn default() -> Self {
Self {
source_uri: None,
source_uri: Url::parse("file:///").unwrap(),
code_resolver: &codelist::NoopResolver {},
id_map: indexmap::IndexSet::default(),
}
Expand Down
75 changes: 47 additions & 28 deletions nusamai-citygml/src/values.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use crate::{CityGmlElement, ParseContext};
pub use chrono::NaiveDate;
use serde::{Deserialize, Serialize};
use std::io::BufRead;
use url::Url;

// type aliases
pub type Date = chrono::NaiveDate;
Expand Down Expand Up @@ -33,27 +34,46 @@ impl CityGmlElement for String {
}
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default, PartialEq, Eq)]
pub struct URI(String);
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
pub struct Uri(url::Url);

impl URI {
pub fn new(s: &str) -> Self {
Self(s.into())
impl Uri {
pub fn new(s: url::Url) -> Self {
Self(s)
}
pub fn value(&self) -> &String {
pub fn value(&self) -> &Url {
&self.0
}
pub fn into_inner(self) -> Url {
self.0
}
}

impl From<url::Url> for Uri {
fn from(url: url::Url) -> Self {
Self(url)
}
}

impl CityGmlElement for URI {
impl Default for Uri {
fn default() -> Self {
Self(Url::parse("file:///default").unwrap())
}
}

impl CityGmlElement for Uri {
#[inline]
fn parse<R: BufRead>(&mut self, st: &mut SubTreeReader<R>) -> Result<(), ParseError> {
self.0.push_str(st.parse_text()?);
let text = st.parse_text()?.to_string();
let base_url = st.context().source_url();
self.0 = base_url
.join(&text)
.map_err(|_| ParseError::InvalidValue("Invalid URI: {text}".to_string()))?;
Ok(())
}

fn into_object(self) -> Option<Value> {
Some(Value::String(self.0))
Some(Value::String(self.0.to_string()))
}

fn collect_schema(_schema: &mut schema::Schema) -> schema::Attribute {
Expand Down Expand Up @@ -88,23 +108,22 @@ impl CityGmlElement for Code {
self.code = code.clone();

if let Some(code_space) = code_space {
if let Some(base_url) = st.context().source_url() {
match st
.context()
.code_resolver()
.resolve(base_url, &code_space, &code)
{
Ok(Some(v)) => {
self.value = v;
return Ok(());
}
Ok(None) => {}
Err(_) => {
// FIXME
log::warn!("Failed to lookup code {} form {}", code, code_space);
self.value = code;
return Ok(());
}
let base_url = st.context().source_url();
match st
.context()
.code_resolver()
.resolve(base_url, &code_space, &code)
{
Ok(Some(v)) => {
self.value = v;
return Ok(());
}
Ok(None) => {}
Err(_) => {
// FIXME
log::warn!("Failed to lookup code {} form {}", code, code_space);
self.value = code;
return Ok(());
}
}
}
Expand Down Expand Up @@ -569,7 +588,7 @@ pub struct GenericAttribute {
pub measure_attrs: Vec<(String, Measure)>,
pub code_attrs: Vec<(String, Code)>,
pub date_attrs: Vec<(String, Date)>,
pub uri_attrs: Vec<(String, URI)>,
pub uri_attrs: Vec<(String, Uri)>,
pub generic_attr_set: Vec<(String, GenericAttribute)>,
}

Expand Down Expand Up @@ -634,7 +653,7 @@ impl CityGmlElement for GenericAttribute {
.into_iter()
.map(|(k, v)| (k, Value::Date(v))),
);
map.extend(self.uri_attrs.into_iter().map(|(k, v)| (k, Value::URI(v))));
map.extend(self.uri_attrs.into_iter().map(|(k, v)| (k, Value::Uri(v))));
map.extend(
self.generic_attr_set
.into_iter()
Expand Down
11 changes: 7 additions & 4 deletions nusamai-citygml/tests/values.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use nusamai_citygml::{
citygml_feature, values, CityGmlElement, CityGmlReader, Date, Measure, ParseContext,
ParseError, Value, URI,
ParseError, Uri, Value,
};
use url::Url;

#[test]
fn parse_date() {
Expand Down Expand Up @@ -79,7 +80,7 @@ fn parse_basic_types() {
#[citygml(path = b"measure")]
measure: Option<Measure>,
#[citygml(path = b"uri")]
uri: Option<URI>,
uri: Option<Uri>,
#[citygml(path = b"date")]
date: Option<Date>,
}
Expand Down Expand Up @@ -109,7 +110,7 @@ fn parse_basic_types() {
assert!((root.measure.as_ref().unwrap().value() - 3.4).abs() < 1e-15);
assert_eq!(root.string.as_ref().unwrap(), "hello");
assert_eq!(
root.uri.as_ref().unwrap().value(),
root.uri.as_ref().unwrap().value().to_string(),
"https://example.com/foo?bar=2000"
);
assert!(root.bool.unwrap());
Expand Down Expand Up @@ -244,7 +245,9 @@ fn generics() {
);
assert_eq!(
data.attributes["u1"],
Value::URI(values::URI::new("https://foo.com/hoge"))
Value::Uri(values::Uri::new(
Url::parse("https://foo.com/hoge").unwrap()
))
);

let Value::Object(set1) = &data.attributes["set1"] else {
Expand Down
34 changes: 18 additions & 16 deletions nusamai-plateau/src/appearance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@

use crate::models::appearance::{self, ParameterizedTexture, SurfaceDataProperty, X3DMaterial};
use hashbrown::HashMap;
use nusamai_citygml::{appearance::TextureAssociation, Color, LocalId, SurfaceSpan, URI};
use nusamai_citygml::{appearance::TextureAssociation, Color, LocalId, SurfaceSpan};
use nusamai_geometry::LineString2;
use std::hash::{Hash, Hasher};
use url::Url;

#[derive(Debug, Default, serde::Serialize, serde::Deserialize)]
pub struct Theme {
Expand Down Expand Up @@ -59,19 +60,20 @@ pub struct AppearanceStore {
/// Texture (CityGML's ParameterizedTexture)
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct Texture {
pub image_uri: String,
// TOOD: other parameters
pub image_url: Url,
// TODO: other parameters
}

impl From<ParameterizedTexture> for Texture {
fn from(src: ParameterizedTexture) -> Self {
let url = src.image_uri.unwrap_or_else(|| {
log::warn!("image_uri is not set");
URI::new("url_not_found.jpg")
});
Self {
image_uri: url.value().to_string(),
}
let image_url = src
.image_uri
.map(|uri| uri.into_inner())
.unwrap_or_else(|| {
log::warn!("image_uri is not set");
url::Url::parse("url_not_found.jpg").unwrap()
});
Self { image_url }
}
}

Expand Down Expand Up @@ -187,13 +189,13 @@ mod tests {

{
app_local.textures.push(Texture {
image_uri: "local1.jpg".to_string(),
image_url: Url::parse("file:///local1.jpg").unwrap(),
});
app_local.textures.push(Texture {
image_uri: "local2.jpg".to_string(),
image_url: Url::parse("file:///local2.jpg").unwrap(),
});
app_local.textures.push(Texture {
image_uri: "local3.jpg".to_string(),
image_url: Url::parse("file:///local3.jpg").unwrap(),
});
app_local.materials.push(Material::default());
app_local.materials.push(Material::default());
Expand All @@ -214,13 +216,13 @@ mod tests {

{
app_global.textures.push(Texture {
image_uri: "global1.jpg".to_string(),
image_url: Url::parse("file:///global1.jpg").unwrap(),
});
app_global.textures.push(Texture {
image_uri: "global2.jpg".to_string(),
image_url: Url::parse("file:///global2.jpg").unwrap(),
});
app_global.textures.push(Texture {
image_uri: "global3.jpg".to_string(),
image_url: Url::parse("file:///global3.jpg").unwrap(),
});
app_global.materials.push(Material::default());
app_global.materials.push(Material::default());
Expand Down
2 changes: 2 additions & 0 deletions nusamai-plateau/src/entity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ use crate::appearance::AppearanceStore;
pub struct Entity {
/// Attribute tree
pub root: Value,
/// Base url of the entity
pub base_url: url::Url,
/// All geometries referenced by the attribute tree
pub geometry_store: Arc<RwLock<GeometryStore>>,
/// All appearances used in this city object
Expand Down
8 changes: 4 additions & 4 deletions nusamai-plateau/src/models/appearance.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use nusamai_citygml::appearance::TextureAssociation;
use nusamai_citygml::{
citygml_feature, citygml_property, CityGmlElement, Code, Color, ColorPlusOpacity, Double01,
LocalId, Point, URI,
LocalId, Point, Uri,
};

type TextureType = String; // TODO?
Expand Down Expand Up @@ -72,7 +72,7 @@ pub struct ParameterizedTexture {
pub is_front: Option<bool>,

#[citygml(path = b"app:imageURI", required)]
pub image_uri: Option<URI>,
pub image_uri: Option<Uri>,

#[citygml(path = b"app:mimeType")]
pub mime_type: Option<Code>,
Expand All @@ -96,7 +96,7 @@ pub struct GeoreferencedTexture {
pub is_front: Option<bool>,

#[citygml(path = b"app:imageURI", required)]
pub image_uri: Option<URI>,
pub image_uri: Option<Uri>,

#[citygml(path = b"app:mimeType")]
pub mime_type: Option<Code>,
Expand All @@ -120,5 +120,5 @@ pub struct GeoreferencedTexture {
pub orientation: Option<TransformationMatrix2x2>,

#[citygml(path = b"app:target")]
pub target: Vec<URI>,
pub target: Vec<Uri>,
}
Loading

0 comments on commit f5d7a43

Please sign in to comment.