-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
nusamai-geometryをボクセルグリッドに変換するためのクレートを実装 (#520)
<!-- Close or Related Issues --> Close #518 ### Description(変更内容) <!-- Please describe the motivation behind this PR and the changes it introduces. --> <!-- 何のために、どのような変更をしますか? --> Minecraft sink実装に向けて、nusamai-geometryをVoxelに変換するための機能を実装しました。 ```rust pub struct Voxel { pub x: i32, pub y: i32, pub z: i32, pub color: [u8; 3], } let mut voxels: HashSet<Voxel> ``` ### Manual Testing(手動テスト) <!-- If manual testing is required, please describe the procedure. --> <!-- 手動での動作確認が必要なら、そのやり方を記述してください。--> 以下のようにテストすることができます。 ```bash cargo test --package nusamai-voxelize --lib -- voxelize::tests --nocapture ``` <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **新機能** - 3Dメッシュをボクセル化し、GLTFファイルに出力する機能を追加しました。 - Minecraftモジュールを新たに追加しました。 - **ドキュメント** - ボクセル化に関する機能と例を提供するドキュメントを追加しました。 <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Taku Fukada <[email protected]>
- Loading branch information
1 parent
afb2b34
commit b56974c
Showing
4 changed files
with
985 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
[package] | ||
name = "nusamai-voxelize" | ||
version = "0.1.0" | ||
edition = "2021" | ||
|
||
[dependencies] | ||
glam = "0.27" | ||
hashbrown = "0.14" | ||
|
||
[dev-dependencies] | ||
nusamai-geometry = { path = "../nusamai-geometry" } | ||
earcut = "0.4" | ||
byteorder = "1.5" | ||
serde_json = "1.0" | ||
indexmap = "2.2" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,239 @@ | ||
use std::{ | ||
fs::File, | ||
io::{BufWriter, Write}, | ||
}; | ||
|
||
use byteorder::{LittleEndian, WriteBytesExt}; | ||
use earcut::{utils3d::project3d_to_2d, Earcut}; | ||
use hashbrown::HashMap; | ||
use indexmap::IndexSet; | ||
use nusamai_geometry::MultiPolygon; | ||
use serde_json::json; | ||
|
||
use nusamai_voxelize::{DdaVoxelizer, MeshVoxelizer}; | ||
|
||
fn main() { | ||
let vertices: Vec<[f64; 3]> = vec![ | ||
// exterior | ||
[21.5, 0.5, 0.5], | ||
[0.5, 0.5, 21.5], | ||
[-20.5, 0.5, 0.5], | ||
[0.5, 0.5, -20.5], | ||
[0.5, 45.5, 0.5], | ||
[0.5, -44.5, 0.5], | ||
]; | ||
|
||
let mut mpoly = MultiPolygon::<u32>::new(); | ||
|
||
// index | ||
mpoly.add_exterior([0, 1, 2, 3]); | ||
mpoly.add_exterior([0, 1, 4]); | ||
mpoly.add_exterior([1, 2, 4]); | ||
mpoly.add_exterior([2, 3, 4]); | ||
mpoly.add_exterior([3, 0, 4]); | ||
mpoly.add_exterior([0, 1, 5]); | ||
mpoly.add_exterior([1, 2, 5]); | ||
mpoly.add_exterior([2, 3, 5]); | ||
mpoly.add_exterior([3, 0, 5]); | ||
|
||
// triangulation | ||
let mut earcutter = Earcut::new(); | ||
let mut buf3d: Vec<[f32; 3]> = Vec::new(); | ||
let mut buf2d: Vec<[f32; 2]> = Vec::new(); | ||
let mut index_buf: Vec<u32> = Vec::new(); | ||
|
||
let mut voxelizer = DdaVoxelizer { | ||
voxels: HashMap::new(), | ||
}; | ||
|
||
for idx_poly in mpoly.iter() { | ||
let poly = idx_poly.transform(|idx| vertices[*idx as usize]); | ||
let num_outer = match poly.hole_indices().first() { | ||
Some(&v) => v as usize, | ||
None => poly.raw_coords().len(), | ||
}; | ||
|
||
for axis in 0..=2 { | ||
buf3d.clear(); | ||
buf3d.extend(poly.raw_coords().iter().map(|v| match axis { | ||
0 => [v[0] as f32, v[1] as f32, v[2] as f32], | ||
1 => [v[1] as f32, v[0] as f32, v[2] as f32], | ||
2 => [v[0] as f32, v[2] as f32, v[1] as f32], | ||
_ => unreachable!(), | ||
})); | ||
if project3d_to_2d(&buf3d, num_outer, &mut buf2d) { | ||
// earcut | ||
earcutter.earcut(buf2d.iter().cloned(), poly.hole_indices(), &mut index_buf); | ||
for indx in index_buf.chunks_exact(3) { | ||
voxelizer.add_triangle(&[ | ||
buf3d[indx[0] as usize], | ||
buf3d[indx[1] as usize], | ||
buf3d[indx[2] as usize], | ||
]); | ||
} | ||
} | ||
} | ||
} | ||
|
||
let occupied_voxels = voxelizer.finalize(); | ||
|
||
// -------------------gltfの作成------------------- | ||
|
||
// voxel is an integer value, but componentType of accessors is 5126 (floating point number), | ||
// and INTEGER type cannot be used due to primitives constraints | ||
|
||
let mut positions = IndexSet::new(); | ||
let mut indices = Vec::new(); | ||
|
||
for (position, _) in occupied_voxels.iter() { | ||
let [x, y, z] = [position[0] as f32, position[1] as f32, position[2] as f32]; | ||
|
||
// Make a voxel cube | ||
let (idx0, _) = positions.insert_full([ | ||
(x + 0.5).to_bits(), | ||
(y - 0.5).to_bits(), | ||
(z + 0.5).to_bits(), | ||
]); | ||
let (idx1, _) = positions.insert_full([ | ||
(x - 0.5).to_bits(), | ||
(y - 0.5).to_bits(), | ||
(z + 0.5).to_bits(), | ||
]); | ||
let (idx2, _) = positions.insert_full([ | ||
(x + 0.5).to_bits(), | ||
(y - 0.5).to_bits(), | ||
(z - 0.5).to_bits(), | ||
]); | ||
let (idx3, _) = positions.insert_full([ | ||
(x - 0.5).to_bits(), | ||
(y - 0.5).to_bits(), | ||
(z - 0.5).to_bits(), | ||
]); | ||
let (idx4, _) = positions.insert_full([ | ||
(x + 0.5).to_bits(), | ||
(y + 0.5).to_bits(), | ||
(z + 0.5).to_bits(), | ||
]); | ||
let (idx5, _) = positions.insert_full([ | ||
(x - 0.5).to_bits(), | ||
(y + 0.5).to_bits(), | ||
(z + 0.5).to_bits(), | ||
]); | ||
let (idx6, _) = positions.insert_full([ | ||
(x + 0.5).to_bits(), | ||
(y + 0.5).to_bits(), | ||
(z - 0.5).to_bits(), | ||
]); | ||
let (idx7, _) = positions.insert_full([ | ||
(x - 0.5).to_bits(), | ||
(y + 0.5).to_bits(), | ||
(z - 0.5).to_bits(), | ||
]); | ||
indices.extend( | ||
[ | ||
idx0, idx1, idx2, idx2, idx1, idx3, idx6, idx5, idx4, idx5, idx6, idx7, idx2, idx3, | ||
idx6, idx7, idx6, idx3, idx4, idx1, idx0, idx1, idx4, idx5, idx0, idx2, idx4, idx6, | ||
idx4, idx2, idx5, idx3, idx1, idx3, idx5, idx7, | ||
] | ||
.iter() | ||
.map(|&idx| idx as u32), | ||
); | ||
} | ||
|
||
let mut min_point = [f32::MAX; 3]; | ||
let mut max_point = [f32::MIN; 3]; | ||
{ | ||
let mut bin_file = BufWriter::new(File::create("output.bin").unwrap()); | ||
for pos in &positions { | ||
min_point[0] = f32::min(min_point[0], f32::from_bits(pos[0])); | ||
min_point[1] = f32::min(min_point[1], f32::from_bits(pos[1])); | ||
min_point[2] = f32::min(min_point[2], f32::from_bits(pos[2])); | ||
max_point[0] = f32::max(max_point[0], f32::from_bits(pos[0])); | ||
max_point[1] = f32::max(max_point[1], f32::from_bits(pos[1])); | ||
max_point[2] = f32::max(max_point[2], f32::from_bits(pos[2])); | ||
|
||
bin_file.write_u32::<LittleEndian>(pos[0]).unwrap(); | ||
bin_file.write_u32::<LittleEndian>(pos[1]).unwrap(); | ||
bin_file.write_u32::<LittleEndian>(pos[2]).unwrap(); | ||
} | ||
|
||
for &idx in &indices { | ||
bin_file.write_u32::<LittleEndian>(idx).unwrap(); | ||
} | ||
} | ||
|
||
// number of voxels x number of vertex coordinates (3) x 4 bytes (f32) | ||
let positions_size = positions.len() * 12; | ||
let indices_size = indices.len() * 4; | ||
let total_size = positions_size + indices_size; | ||
|
||
// make glTF | ||
let gltf_json = json!( { | ||
"asset": { | ||
"version": "2.0", | ||
}, | ||
"scene": 0, | ||
"scenes": [ | ||
{ | ||
"nodes": [0], | ||
}, | ||
], | ||
"nodes": [ | ||
{"mesh": 0}, | ||
], | ||
"meshes": [ | ||
{ | ||
"primitives": [ | ||
{ | ||
"attributes": {"POSITION": 0}, | ||
"indices": 1, | ||
"mode": 4, // TRIANGLES | ||
}, | ||
], | ||
}, | ||
], | ||
"buffers": [ | ||
{ | ||
"uri": "./output.bin", | ||
"byteLength": total_size, | ||
}, | ||
], | ||
"bufferViews": [ | ||
{ | ||
"buffer": 0, | ||
"byteOffset": 0, | ||
"byteLength": positions_size, | ||
"target": 34962, | ||
}, | ||
{ | ||
"buffer": 0, | ||
"byteOffset": positions_size, | ||
"byteLength": indices_size, | ||
"target": 34963, | ||
}, | ||
], | ||
"accessors": [ | ||
{ | ||
"bufferView": 0, | ||
"byteOffset": 0, | ||
"componentType": 5126, // FLOAT | ||
"count": positions.len(), | ||
"type": "VEC3", | ||
"min": [min_point[0], min_point[1], min_point[2]], | ||
"max": [max_point[0], max_point[1], max_point[2]], | ||
}, | ||
{ | ||
"bufferView": 1, | ||
"byteOffset": 0, | ||
"componentType": 5125, // UNSIGNED_INT | ||
"count": indices.len(), | ||
"type": "SCALAR", | ||
}, | ||
], | ||
}); | ||
|
||
// gltfファイルを出力 | ||
println!("write glTF"); | ||
let mut gltf_file = File::create("output.gltf").unwrap(); | ||
let _ = gltf_file.write_all(gltf_json.to_string().as_bytes()); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
mod voxelize; | ||
|
||
pub use voxelize::*; |
Oops, something went wrong.