Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit fbd3389

Browse files
authored
Merge pull request #10 from swiftcoder/0.1-alpha.0
Extended Marching Cubes and Dual Contouring
2 parents f9f4750 + e2888e8 commit fbd3389

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+4447
-993
lines changed

.license_template

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Copyright 2021 Tristam MacDonald
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.

Cargo.toml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ license = "Apache-2.0"
66
name = "isosurface"
77
readme = "README.md"
88
repository = "https://github.com/swiftcoder/isosurface"
9-
version = "0.0.4"
9+
version = "0.1.0-alpha.0"
1010

1111
[dev-dependencies]
1212
cgmath = "^0.17"
@@ -15,12 +15,12 @@ glium = "^0.26"
1515
glium_text_rusttype = "^0.3"
1616

1717
[profile.dev]
18-
opt-level = 2
18+
opt-level = 3
1919

2020
[profile.release]
21-
opt-level = 3
2221
lto = true
22+
opt-level = 3
2323

2424
[[bench]]
25-
name = "isosurface"
2625
harness = false
26+
name = "isosurface"

README.md

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,27 @@
11
# Isosurface
2-
Isosurface extraction algorithms for Rust. Currently only a few techniques are implemented.
2+
Isosurface extraction algorithms implemented in Rust. The classical Marching Cubes and Dual Contouring techniques are included, along with more modern variations on the theme.
33

4-
This crate intentionally has no dependencies to keep the footprint of the library small. The examples rely on the `glium`, `glium_text_rusttype`, and `cgmath` crates.
4+
In the interest of education, the documentation for each extraction algorithm links to the relevant academic papers.
55

6-
# Marching Cubes
7-
The Marching Cubes implementation produces perfectly indexed meshes with few duplicate vertices, through the use of a (fairly involved) index caching system. The complexity of the cache could no doubt be reduced through some clever arithmetic, but it is not currently a bottleneck.
6+
## Example programs
7+
`cargo run --example sampler` will execute the sampler, which allows you to compare a variety of algorithms and implicit surfaces.
88

9-
The implementation has been optimised for performance, with memory use kept as a low as possible considering. For an NxNxN voxel chunk, it will allocate roughly NxN of f32 storage for isosurface values, and Nx(N+1) of u32 storage for the index cache.
9+
`cargo run --example deferred_rasterisation` will execute a demonstration of GPU-side deferred rasterisation from point clouds. This is a technique pioneered by Gavan Woolery, of [Voxel Quest](https://www.voxelquest.com) fame.
1010

11-
Indices are 32-bit because for chunks of 32x32 and larger you'll typically end up with more than 65k vertices. If you are targeting a mobile platform that supports only 16-bit indices, you'll need to use smaller chunk sizes, and truncate on the output side.
11+
## Dependencies
12+
This library intentionally has no dependencies. While that requires some redevelopment of common code (i.e. the Vec3 type), it keeps the footprint of the library small, and compile times low for consuming crates. The examples do however rely on the `glium`, `glium_text_rusttype`, and `cgmath` crates, to avoid reinventing the world.
1213

13-
# Linear Hashed Marching Cubes
14-
A very efficient algorithm using interleaved integer coordinates to represent octree cells, and storing them in a hash table. Results in better mesh quality than regular marching cubes, and is significantly faster. Memory usage is less predictable, but shouldn't be significantly higher than standard marching cubes.
15-
16-
# Point Clouds and Deferred Rasterisation
17-
Point cloud extraction is typically not all that useful, given that point clouds don't contain any data about the actual surface. However, Gavan Woolery (gavanw@) posted an interesting image of reconstructing surface data in image space on the GPU, so I've added a simple example of that.
14+
## 32-bit indices
15+
For simplicity vertex indices have been fixed at 32-bits, because for chunks of 32x32x32 and larger you'll often end up with more than 65k vertices. If you are targeting a mobile platform that supports only 16-bit indices, you'll need to keep to smaller chunk sizes, or split the mesh on the output side.
1816

19-
# Why are optimisations enabled in debug builds?
20-
Without optimisations enabled, debug builds are 70x slower (1 minute to extract a 256^3 volume, versus ~800 milliseconds).
17+
## Why are optimisations enabled in debug builds?
18+
Without optimisations enabled, debug builds are around 70x slower. The implementation relies on a lot of nested for loops over integer ranges, and the range iterators themselves entirely dominate the CPU profiles in unoptimised builds.
2119

22-
The marching cubes implementation relies on a lot of nested for loops over integer ranges, and the range iterators themselves entirely dominate the CPU profiles in unoptimised builds. While this could likely be worked around by converting the `for 0..8` style of loop to a while loop with manual counter, that seems ugly and distinctly not in the spirit of rust. I'd rather leave optimisations enabled, and wait for the compiler to become better at handling iterators.
20+
While this can be worked around by converting the `for 0..8` style of loop to a while loop with manual counter, the result is quite unpleasant, and distinctly not in the spirit of rust. I'd rather leave optimisations enabled, and wait for the compiler to become better at handling iterators in debug builds.
21+
22+
If you take a dependency on this crate and run into the same issue, you can tell Cargo to compile just this one crate in release mode, by adding the following to your `Cargo.toml`:
23+
24+
```
25+
[profile.dev.package.isosurface]
26+
opt-level = 3
27+
```

benches/isosurface.rs

Lines changed: 28 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,45 @@
1+
// Copyright 2021 Tristam MacDonald
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
115
use criterion::{criterion_group, criterion_main, Criterion};
216
use isosurface::{
3-
linear_hashed_marching_cubes::LinearHashedMarchingCubes, marching_cubes::MarchingCubes,
4-
source::Source,
17+
distance::Signed, extractor::IndexedVertices, implicit::Torus, sampler::Sampler,
18+
LinearHashedMarchingCubes, MarchingCubes,
519
};
620

7-
fn torus(x: f32, y: f32, z: f32) -> f32 {
8-
const R1: f32 = 1.0 / 4.0;
9-
const R2: f32 = 1.0 / 10.0;
10-
let q_x = ((x * x + y * y).sqrt()).abs() - R1;
11-
let len = (q_x * q_x + z * z).sqrt();
12-
len - R2
13-
}
14-
15-
pub struct Torus {}
16-
17-
impl Source for Torus {
18-
fn sample(&self, x: f32, y: f32, z: f32) -> f32 {
19-
torus(x - 0.5, y - 0.5, z - 0.5)
20-
}
21-
}
22-
2321
fn marching_cubes() {
24-
let torus = Torus {};
22+
let torus = Torus::new(0.25, 0.1);
23+
let sampler = Sampler::new(&torus);
24+
2525
let mut vertices = vec![];
2626
let mut indices = vec![];
27+
let mut extractor = IndexedVertices::new(&mut vertices, &mut indices);
2728

28-
let mut marching_cubes = MarchingCubes::new(256);
29-
marching_cubes.extract(&torus, &mut vertices, &mut indices);
29+
let mut marching_cubes = MarchingCubes::<Signed>::new(128);
30+
marching_cubes.extract(&sampler, &mut extractor);
3031
}
3132

3233
fn linear_hashed_marching_cubes() {
33-
let torus = Torus {};
34+
let torus = Torus::new(0.25, 0.1);
35+
let sampler = Sampler::new(&torus);
36+
3437
let mut vertices = vec![];
3538
let mut indices = vec![];
39+
let mut extractor = IndexedVertices::new(&mut vertices, &mut indices);
3640

37-
let mut marching_cubes = LinearHashedMarchingCubes::new(8);
38-
marching_cubes.extract(&torus, &mut vertices, &mut indices);
41+
let mut marching_cubes = LinearHashedMarchingCubes::new(7);
42+
marching_cubes.extract(&sampler, &mut extractor);
3943
}
4044

4145
fn marching_cubes_benchmark(c: &mut Criterion) {

examples/common/mod.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2018 Tristam MacDonald
1+
// Copyright 2021 Tristam MacDonald
22
//
33
// Licensed under the Apache License, Version 2.0 (the "License");
44
// you may not use this file except in compliance with the License.
@@ -18,10 +18,10 @@ pub mod text;
1818
use std::mem;
1919
use std::slice;
2020

21-
/// This is used to reinterpret slices of floats as slices of repr(C) structs, without any
22-
/// copying. It is optimal, but it is also punching holes in the type system. I hope that Rust
23-
/// provides safe functionality to handle this in the future. In the meantime, reproduce
24-
/// this workaround at your own risk.
21+
/// This is used to reinterpret slices of floats as slices of repr(C) structs,
22+
/// without any copying. It is optimal, but it is also punching holes in the
23+
/// type system. I hope that Rust provides safe functionality to handle this in
24+
/// the future. In the meantime, reproduce this workaround at your own risk.
2525
pub fn reinterpret_cast_slice<S, T>(input: &[S]) -> &[T] {
2626
let length_in_bytes = input.len() * mem::size_of::<S>();
2727
let desired_length = length_in_bytes / mem::size_of::<T>();

examples/common/sources.rs

Lines changed: 30 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2018 Tristam MacDonald
1+
// Copyright 2021 Tristam MacDonald
22
//
33
// Licensed under the Apache License, Version 2.0 (the "License");
44
// you may not use this file except in compliance with the License.
@@ -13,68 +13,45 @@
1313
// limitations under the License.
1414

1515
//! Isosurface definitions for use in multiple examples
16+
use isosurface::{
17+
distance::{Directed, Signed},
18+
math::Vec3,
19+
source::{HermiteSource, ScalarSource, VectorSource},
20+
};
1621

17-
use isosurface::source::Source;
22+
pub trait AllSources: ScalarSource + VectorSource + HermiteSource {}
1823

19-
/// The distance-field equation for a torus
20-
fn torus(x: f32, y: f32, z: f32) -> f32 {
21-
const R1: f32 = 1.0 / 4.0;
22-
const R2: f32 = 1.0 / 10.0;
23-
let q_x = ((x * x + y * y).sqrt()).abs() - R1;
24-
let len = (q_x * q_x + z * z).sqrt();
25-
len - R2
26-
}
27-
28-
pub struct Torus {}
29-
30-
impl Source for Torus {
31-
fn sample(&self, x: f32, y: f32, z: f32) -> f32 {
32-
torus(x - 0.5, y - 0.5, z - 0.5)
33-
}
34-
}
24+
impl<S: ScalarSource + VectorSource + HermiteSource> AllSources for S {}
3525

36-
fn abs(x: f32, y: f32, z: f32) -> (f32, f32, f32) {
37-
(
38-
if x > 0.0 { x } else { -x },
39-
if y > 0.0 { y } else { -y },
40-
if z > 0.0 { z } else { -z },
41-
)
26+
pub struct DemoSource<'a> {
27+
pub source: Box<dyn 'a + AllSources>,
4228
}
4329

44-
fn max(px: f32, py: f32, pz: f32, qx: f32, qy: f32, qz: f32) -> (f32, f32, f32) {
45-
(
46-
if px > qx { px } else { qx },
47-
if py > qy { py } else { qy },
48-
if pz > qz { pz } else { qz },
49-
)
50-
}
51-
52-
/// The distance field equation for a cube
53-
fn cube(px: f32, py: f32, pz: f32, bx: f32, by: f32, bz: f32) -> f32 {
54-
let (ax, ay, az) = abs(px, py, pz);
55-
let (dx, dy, dz) = (ax - bx, ay - by, az - bz);
56-
let (mx, my, mz) = max(dx, dy, dz, 0.0, 0.0, 0.0);
57-
let l = (mx * mx + my * my + mz * mz).sqrt();
58-
dx.max(dz.max(dy)).min(0.0) + l
30+
impl<'a> DemoSource<'a> {
31+
pub fn new<S: 'a + AllSources>(source: S) -> Self {
32+
Self {
33+
source: Box::new(source),
34+
}
35+
}
5936
}
6037

61-
/// The distance field equation for a sphere
62-
fn sphere(x: f32, y: f32, z: f32, r: f32) -> f32 {
63-
(x * x + y * y + z * z).sqrt() - r
38+
impl<'a> ScalarSource for DemoSource<'a> {
39+
fn sample_scalar(&self, p: Vec3) -> Signed {
40+
let q = p - Vec3::from_scalar(0.5);
41+
self.source.sample_scalar(q)
42+
}
6443
}
6544

66-
/// Subtract one distance field from another (i.e. CSG difference operation)
67-
fn subtract(d1: f32, d2: f32) -> f32 {
68-
d2.max(-d1)
45+
impl<'a> VectorSource for DemoSource<'a> {
46+
fn sample_vector(&self, p: Vec3) -> Directed {
47+
let q = p - Vec3::from_scalar(0.5);
48+
self.source.sample_vector(q)
49+
}
6950
}
7051

71-
pub struct CubeSphere {}
72-
73-
impl Source for CubeSphere {
74-
fn sample(&self, x: f32, y: f32, z: f32) -> f32 {
75-
subtract(
76-
sphere(x - 0.5, y - 0.5, z - 0.5, 0.25),
77-
cube(x - 0.5, y - 0.5, z - 0.5, 0.2, 0.2, 0.2),
78-
)
52+
impl<'a> HermiteSource for DemoSource<'a> {
53+
fn sample_normal(&self, p: Vec3) -> Vec3 {
54+
let q = p - Vec3::from_scalar(0.5);
55+
self.source.sample_normal(q)
7956
}
8057
}

examples/common/text.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2018 Tristam MacDonald
1+
// Copyright 2021 Tristam MacDonald
22
//
33
// Licensed under the Apache License, Version 2.0 (the "License");
44
// you may not use this file except in compliance with the License.
@@ -13,11 +13,11 @@
1313
// limitations under the License.
1414

1515
//! Conveniences to make the `glium_text` API easier to use in samples.
16-
1716
use cgmath::{Matrix4, Vector3};
1817

19-
/// Produce a transform matrix that will display text at offset column `x`, row `y`, in a
20-
/// display-filling coordinate space N characters wide and N*aspect rows high.
18+
/// Produce a transform matrix that will display text at offset column `x`, row
19+
/// `y`, in a display-filling coordinate space N characters wide and N*aspect
20+
/// rows high.
2121
pub fn layout_text(characters_per_row: f32, aspect: f32, x: f32, y: f32) -> Matrix4<f32> {
2222
let inv_scale = 2.0 / characters_per_row;
2323
Matrix4::from_translation(Vector3::new(-1.0, -1.0, 0.0))

0 commit comments

Comments
 (0)