Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

3dtiles: glTFにマテリアル (PBR Material, Texture) を付与する #308

Merged
merged 15 commits into from
Feb 21, 2024
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 @@
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 @@
}
}

#[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)
}

Check warning on line 55 in nusamai-citygml/src/values.rs

View check run for this annotation

Codecov / codecov/patch

nusamai-citygml/src/values.rs#L53-L55

Added lines #L53 - L55 were not covered by tests
}

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 @@
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 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 @@
.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 @@
/// 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()

Check warning on line 74 in nusamai-plateau/src/appearance.rs

View check run for this annotation

Codecov / codecov/patch

nusamai-plateau/src/appearance.rs#L73-L74

Added lines #L73 - L74 were not covered by tests
});
Self { image_url }
}
}

Expand Down Expand Up @@ -187,13 +189,13 @@

{
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 @@

{
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
Loading