Skip to content

Commit

Permalink
3D Tiles: Tileset JSON と Subtree JSON のモデルを作成 (#94)
Browse files Browse the repository at this point in the history
Tileset JSON(と、今回は使わないと思いますが Implicit Tiling 用の Subtree JSON
も)用のモデルを作りました。


[CesiumGS/3d-tiles-samples](https://github.com/CesiumGS/3d-tiles-samples)
にあるサンプルの `tileset.json` と、`*.subtree` をすべて読み込めることだけテストしてあります。

Closes #45
  • Loading branch information
ciscorn authored Dec 21, 2023
1 parent 0a372cb commit a29126b
Show file tree
Hide file tree
Showing 174 changed files with 6,335 additions and 26 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@ members = [
"nusamai-mvt",
"nusamai",
"nusamai-projection",
"nusamai-3dtiles",
]
resolver = "2"
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,10 @@
- [`nusamai-plateau`](./nusamai-plateau/) — PLATEAU CityGML パーサ
- [`nusamai-projection`](./nusamai-projection/) — 投影法変換
- 変換先形式:
- [`nusamai-geojson`](./nusamai-geojson/) — GeoJSON
- [`nusamai-3dtiles`](./nusamai-3dtiles/) — 3D Tiles
- [`nusamai-mvt`](./nusamai-mvt/) — Mapbox Vector Tile (MVT)
- [`nusamai-gltf`](./nusamai-gltf/) — glTF
- [`nusamai-geojson`](./nusamai-geojson/) — GeoJSON

### 外部リポジトリ

Expand Down
15 changes: 15 additions & 0 deletions nusamai-3dtiles/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[package]
name = "nusamai-3dtiles"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
serde = { version = "1.0.192", features = ["derive"] }
serde_json = { version = "1.0.108", features = ["float_roundtrip"] }
serde_repr = "0.1.17"
nusamai-gltf = { path = "../nusamai-gltf" }

[dev-dependencies]
glob = "0.3.1"
3 changes: 3 additions & 0 deletions nusamai-3dtiles/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
mod models;

pub use models::*;
3 changes: 3 additions & 0 deletions nusamai-3dtiles/src/models/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pub mod styling;
pub mod subtree;
pub mod tileset;
99 changes: 99 additions & 0 deletions nusamai-3dtiles/src/models/styling.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
//! Tileset style files
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

use serde_json::Value;

/// A 3D Tiles style.
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
#[serde(default)]
struct Style {
/// A dictionary object of `expression` strings mapped to a variable name key that may be referenced throughout the style. If an expression references a defined variable, it is replaced with the evaluated result of the corresponding expression.
#[serde(skip_serializing_if = "Option::is_none")]
defines: Option<HashMap<String, Expression>>, // default: None

show: BooleanExpressionOrConditions, // default: "true"

/// A `color expression` or `conditions` property which determines the color blended with the feature's intrinsic color.
color: ColorExpressionOrConditions, // default: ColorExpression("color('#FFFFFF')")

/// A `meta` object which determines the values of non-visual properties of the feature.
#[serde(skip_serializing_if = "Option::is_none")]
meta: Option<Meta>,

/// A `number expression` or `conditions` property which determines the size of the points in pixels.
point_size: NumberExpressionOrConditions, // default: 1.0,

/// Dictionary object with extension-specific objects.
#[serde(skip_serializing_if = "Option::is_none")]
extensions: Option<HashMap<String, Value>>,

/// Application-specific data.
#[serde(default, skip_serializing_if = "Option::is_none")]
extra: Option<Value>,
}

impl Default for Style {
fn default() -> Self {
Self {
defines: None,
show: BooleanExpressionOrConditions::BooleanExpression(Value::Bool(true)),
color: ColorExpressionOrConditions::ColorExpression("color('#FFFFFF')".to_string()),
meta: None,
point_size: NumberExpressionOrConditions::NumberExpression(
serde_json::Number::from_f64(1.0).into(),
),
extensions: None,
extra: None,
}
}
}

/// A valid 3D Tiles style expression. Details are described in the 3D Tiles Styling specification.
type Expression = String;

/// A boolean or string with a 3D Tiles style expression that evaluates to a boolean. Details are described in the 3D Tiles Styling specification.
type BooleanExpression = Value; // String | bool

/// 3D Tiles style `expression` that evaluates to a Color. Details are described in the 3D Tiles Styling specification.
type ColorExpression = String;

/// 3D Tiles style expression that evaluates to a number. Details are described in the 3D Tiles Styling specification.
type NumberExpression = Value; // float | str

/// A series of property names and the `expression` to evaluate for the value of that property."""
type Meta = HashMap<String, Expression>;

#[derive(Serialize, Deserialize, Debug)]
#[serde(untagged)]
pub enum BooleanExpressionOrConditions {
BooleanExpression(BooleanExpression),
Conditions(Conditions),
}

#[derive(Serialize, Deserialize, Debug)]
#[serde(untagged)]
pub enum NumberExpressionOrConditions {
NumberExpression(NumberExpression),
Conditions(Conditions),
}

#[derive(Serialize, Deserialize, Debug)]
#[serde(untagged)]
pub enum ColorExpressionOrConditions {
ColorExpression(ColorExpression),
Conditions(Conditions),
}

/// A series of conditions evaluated in order, like a series of if...else statements that result in an expression being evaluated.
#[derive(Serialize, Deserialize, Debug, Default)]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
pub struct Conditions {
/// A series of boolean conditions evaluated in order. For the first one that evaluates to true, its value, the 'result' (which is also an expression), is evaluated and returned. Result expressions shall all be the same type. If no condition evaluates to true, the result is `undefined`. When conditions is `undefined`, `null`, or an empty object, the result is `undefined`.
#[serde(skip_serializing_if = "Option::is_none")]
conditions: Option<Vec<[Expression; 2]>>,
}
139 changes: 139 additions & 0 deletions nusamai-3dtiles/src/models/subtree.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
//! Subtree JSON for the Implicit Tiling.
use std::collections::HashMap;

use nusamai_gltf::extensions::gltf::ext_structural_metadata;
use serde::{Deserialize, Serialize};

use super::tileset;
use serde_json::Value;

/// An object describing the availability of tiles and content in a subtree, as well as availability of children subtrees. May also store metadata for available tiles and content.
#[derive(Serialize, Deserialize, Debug, Default)]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
pub struct Subtree {
/// An array of buffers.
#[serde(skip_serializing_if = "Option::is_none")]
buffers: Option<Vec<Buffer>>,

/// An array of buffer views.
#[serde(skip_serializing_if = "Option::is_none")]
buffer_views: Option<Vec<BufferView>>,

/// An array of property tables.
#[serde(skip_serializing_if = "Option::is_none")]
property_tables: Option<Vec<ext_structural_metadata::PropertyTable>>,

/// The availability of tiles in the subtree. The availability bitstream is a 1D boolean array where tiles are ordered by their level in the subtree and Morton index within that level. A tile's availability is determined by a single bit, 1 meaning a tile exists at that spatial index, and 0 meaning it does not. The number of elements in the array is `(N^subtreeLevels - 1)/(N - 1)` where N is 4 for subdivision scheme `QUADTREE` and 8 for `OCTREE`. Availability may be stored in a buffer view or as a constant value that applies to all tiles. If a non-root tile's availability is 1 its parent tile's availability shall also be 1. `tileAvailability.constant: 0` is disallowed, as subtrees shall have at least one tile.
tile_availability: Availability, // required

/// An array of content availability objects. If the tile has a single content this array will have one element; if the tile has multiple contents - as supported by 3DTILES_multiple_contents and 3D Tiles 1.1 - this array will have multiple elements.
#[serde(skip_serializing_if = "Option::is_none")]
content_availability: Option<Vec<Availability>>,

/// The availability of children subtrees. The availability bitstream is a 1D boolean array where subtrees are ordered by their Morton index in the level of the tree immediately below the bottom row of the subtree. A child subtree's availability is determined by a single bit, 1 meaning a subtree exists at that spatial index, and 0 meaning it does not. The number of elements in the array is `N^subtreeLevels` where N is 4 for subdivision scheme `QUADTREE` and 8 for `OCTREE`. Availability may be stored in a buffer view or as a constant value that applies to all child subtrees. If availability is 0 for all child subtrees, then the tileset does not subdivide further.
child_subtree_availability: Availability, // required

/// Index of the property table containing tile metadata. Tile metadata only exists for available tiles and is tightly packed by increasing tile index. To access individual tile metadata, implementations may create a mapping from tile indices to tile metadata indices.
#[serde(skip_serializing_if = "Option::is_none")]
tile_metadata: Option<u32>,

/// An array of indexes to property tables containing content metadata. If the tile has a single content this array will have one element; if the tile has multiple contents - as supported by 3DTILES_multiple_contents and 3D Tiles 1.1 - this array will have multiple elements. Content metadata only exists for available contents and is tightly packed by increasing tile index. To access individual content metadata, implementations may create a mapping from tile indices to content metadata indices.
#[serde(skip_serializing_if = "Option::is_none")]
content_metadata: Option<Vec<u32>>,

/// Subtree metadata encoded in JSON.
#[serde(skip_serializing_if = "Option::is_none")]
subtree_metadata: Option<tileset::MetadataEntity>,

/// Dictionary object with extension-specific objects.
#[serde(skip_serializing_if = "Option::is_none")]
extensions: Option<HashMap<String, Value>>,

/// Application-specific data.
#[serde(default, skip_serializing_if = "Option::is_none")]
extra: Option<Value>,
}

/// An object describing the availability of a set of elements.
#[derive(Serialize, Deserialize, Debug, Default)]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
pub struct Availability {
/// Index of a buffer view that indicates whether each element is available. The bitstream conforms to the boolean array encoding described in the 3D Metadata specification. If an element is available, its bit is 1, and if it is unavailable, its bit is 0.
///
/// Either bitstream or constant is required.
#[serde(skip_serializing_if = "Option::is_none")]
bitstream: Option<u32>,

/// A number indicating how many 1 bits exist in the availability bitstream.
#[serde(skip_serializing_if = "Option::is_none")]
available_count: Option<u32>,

/// Integer indicating whether all of the elements are available (1) or all are unavailable (0).
///
/// Either bitstream or constant is required.
#[serde(skip_serializing_if = "Option::is_none")]
constant: Option<u8>,

/// Dictionary object with extension-specific objects.
#[serde(skip_serializing_if = "Option::is_none")]
extensions: Option<HashMap<String, Value>>,

/// Application-specific data.
#[serde(default, skip_serializing_if = "Option::is_none")]
extra: Option<Value>,
}

/// A buffer is a binary blob. It is either the binary chunk of the subtree file, or an external buffer referenced by a URI.
#[derive(Serialize, Deserialize, Debug, Default)]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
pub struct Buffer {
/// The URI (or IRI) of the file that contains the binary buffer data. Relative paths are relative to the file containing the buffer JSON. `uri` is required when using the JSON subtree format and not required when using the binary subtree format - when omitted the buffer refers to the binary chunk of the subtree file. Data URIs are not allowed.
#[serde(skip_serializing_if = "Option::is_none")]
uri: Option<String>,

/// The length of the buffer in bytes.
byte_length: u32, // >= 1, required

/// The name of the buffer.
#[serde(skip_serializing_if = "Option::is_none")]
name: Option<String>,

/// Dictionary object with extension-specific objects.
#[serde(skip_serializing_if = "Option::is_none")]
extensions: Option<HashMap<String, Value>>,

/// Application-specific data.
#[serde(default, skip_serializing_if = "Option::is_none")]
extra: Option<Value>,
}

///A contiguous subset of a buffer
#[derive(Serialize, Deserialize, Debug, Default)]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
pub struct BufferView {
// The index of the buffer.
buffer: u32, // required

/// The offset into the buffer in bytes.
byte_offset: u32, // required

/// The total byte length of the buffer view.
byte_length: u32, // >= 1, required

/// The name of the `bufferView`.
#[serde(skip_serializing_if = "Option::is_none")]
name: Option<String>,

/// Dictionary object with extension-specific objects.
#[serde(skip_serializing_if = "Option::is_none")]
extensions: Option<HashMap<String, Value>>,

/// Application-specific data.
#[serde(default, skip_serializing_if = "Option::is_none")]
extra: Option<Value>,
}
Loading

0 comments on commit a29126b

Please sign in to comment.