seevol 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- seevol-0.1.0/.gitattributes +2 -0
- seevol-0.1.0/.gitignore +8 -0
- seevol-0.1.0/CMakeLists.txt +33 -0
- seevol-0.1.0/LICENSE +21 -0
- seevol-0.1.0/PKG-INFO +132 -0
- seevol-0.1.0/README.md +116 -0
- seevol-0.1.0/examples/quickstart.py +17 -0
- seevol-0.1.0/examples/visualiser_example.py +11 -0
- seevol-0.1.0/images/downard_bias.png +0 -0
- seevol-0.1.0/images/porespy.png +0 -0
- seevol-0.1.0/images/pyvista_100cubed.png +0 -0
- seevol-0.1.0/images/readme_banner.png +0 -0
- seevol-0.1.0/images/realistic_nest.png +0 -0
- seevol-0.1.0/images/test_200cubed.png +0 -0
- seevol-0.1.0/pyproject.toml +36 -0
- seevol-0.1.0/src/seevol/__init__.py +11 -0
- seevol-0.1.0/src/seevol/generator.py +74 -0
- seevol-0.1.0/src/seevol/occlusion.cpp +281 -0
- seevol-0.1.0/src/seevol/visualiser.py +90 -0
- seevol-0.1.0/uv.lock +1969 -0
seevol-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
cmake_minimum_required(VERSION 3.15)
|
|
2
|
+
project(seevol)
|
|
3
|
+
|
|
4
|
+
# C++ 17 standard
|
|
5
|
+
set(CMAKE_CXX_STANDARD 17)
|
|
6
|
+
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|
7
|
+
|
|
8
|
+
# require pybind
|
|
9
|
+
find_package(pybind11 REQUIRED)
|
|
10
|
+
|
|
11
|
+
# create module from .cpp
|
|
12
|
+
pybind11_add_module(voxel_occlusion src/seevol/occlusion.cpp)
|
|
13
|
+
|
|
14
|
+
# for multi-threading the hot loop in is_visible:
|
|
15
|
+
# macOS: auto-locate Homebrew's libomp (no-op elsewhere)
|
|
16
|
+
if(APPLE)
|
|
17
|
+
execute_process(COMMAND brew --prefix libomp
|
|
18
|
+
OUTPUT_VARIABLE HOMEBREW_LIBOMP
|
|
19
|
+
OUTPUT_STRIP_TRAILING_WHITESPACE
|
|
20
|
+
ERROR_QUIET)
|
|
21
|
+
if(HOMEBREW_LIBOMP)
|
|
22
|
+
list(APPEND CMAKE_PREFIX_PATH "${HOMEBREW_LIBOMP}")
|
|
23
|
+
endif()
|
|
24
|
+
endif()
|
|
25
|
+
find_package(OpenMP)
|
|
26
|
+
if(OpenMP_CXX_FOUND)
|
|
27
|
+
target_link_libraries(voxel_occlusion PRIVATE OpenMP::OpenMP_CXX)
|
|
28
|
+
else()
|
|
29
|
+
message(WARNING "OpenMP not found: building serial (non-threaded and slower) version. Please install!")
|
|
30
|
+
endif()
|
|
31
|
+
|
|
32
|
+
# once compiled so an .so, add to the seevol package folder
|
|
33
|
+
install(TARGETS voxel_occlusion DESTINATION seevol)
|
seevol-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 [Archie Benn]
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
seevol-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: seevol
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: All-pairs visibility analysis on voxel volumes (Amanatides & Woo, C++)
|
|
5
|
+
Author-Email: Archie Benn <sj19031@bristol.ac.uk>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Classifier: Programming Language :: C++
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Project-URL: Repository, https://github.com/archiebenn/seeVol
|
|
12
|
+
Requires-Python: >=3.10
|
|
13
|
+
Requires-Dist: numpy
|
|
14
|
+
Requires-Dist: pyvista
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
|
|
17
|
+
# seeVol
|
|
18
|
+
|
|
19
|
+
<p align="center">
|
|
20
|
+
<img src="images/readme_banner.png" alt="3D visualisation of an artificial ant nest volume coloured by voxel visibility" width="800">
|
|
21
|
+
</p>
|
|
22
|
+
|
|
23
|
+
`seevol` analyses line-of-sight visibility in 3D binary volumes, where 0 represents a solid/opaque voxel, and 1 represents open/transparent voxels.
|
|
24
|
+
|
|
25
|
+
The core functionality is based on the Amanatides & Woo voxel traversal algorithm <sup>1</sup> and is implemented in C++ to compute straight line visibility between all pairs of open (non solid) voxels in a given volume.
|
|
26
|
+
|
|
27
|
+
This tool tests visibility between **all** open pairs throughout the volume, which permits per-voxel visibility analysis metrics to be calculated downstream for every open voxel.
|
|
28
|
+
|
|
29
|
+
> [!WARNING]
|
|
30
|
+
> `seevol` computes all-pairs visibility, which scales as O(open_voxels<sup>2</sup>). This becomes slow or memory-limiting beyond a few hundred thousand open voxels — see [Limitations](#limitations) before running on large volumes. Downsampling the volume is likely to help but will impact results.
|
|
31
|
+
|
|
32
|
+
## Dependencies
|
|
33
|
+
|
|
34
|
+
A C++17 or greater build toolchain is required for source installation, alongside Python >=3.10.
|
|
35
|
+
|
|
36
|
+
Pyvista is a dependency for visualisations of the visibility metrics output onto the 3D volume itself (see [this visual example](examples/visualiser_example.py) for use).
|
|
37
|
+
|
|
38
|
+
## Installation
|
|
39
|
+
|
|
40
|
+
To install seevol:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
pip install seevol
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
or for uv users:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
uv pip install seevol
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Usage
|
|
53
|
+
|
|
54
|
+
The core function `visible_pairs()` accepts a 3D binary (0 = solid, 1 = open) NumPy array as input and outputs two NumPy arrays:
|
|
55
|
+
|
|
56
|
+
- `coords` — shape (N, 3), the open voxel coordinates in the volume
|
|
57
|
+
- `edges` — shape (M, 2), pairs of indices into `coords` for voxels that are mutually visible
|
|
58
|
+
|
|
59
|
+
For example, if `edges[0]` is `[2, 7]`,the visibility edge connects `coords[2]` and `coords[7]` and these two voxels are visible to each other via direct line of sight.
|
|
60
|
+
|
|
61
|
+
Users can load .npy files in Python using `np.load()` and pass the result to `visible_pairs()`.
|
|
62
|
+
|
|
63
|
+
Downstream analyses or visualisations are then possible from this output, such as visualising a 'visibility heatmap' using Pyvista (see below)
|
|
64
|
+
|
|
65
|
+
## Limitations
|
|
66
|
+
|
|
67
|
+
This tool currently uses all-pair calculation using voxel ray traversal. Because of this, the ray traversal algorithm scales as O(open_voxels<sup>2</sup>) and is therefore quickly unusable for large volumes.
|
|
68
|
+
|
|
69
|
+
A hard limit is imposed in the C++ source file which limits open voxels to 1,000,000. In reality, volumes with over 500,000 open voxels are likely to significant memory issues as very large visible edges will be stored. Methods to reduce this runtime will hopefully be introduced over time.
|
|
70
|
+
|
|
71
|
+
## Minimal example
|
|
72
|
+
|
|
73
|
+
The [quickstart example](examples/quickstart.py) creates a small volume of open voxels with one solid voxel blocking visibility:
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
import numpy as np
|
|
77
|
+
import seevol
|
|
78
|
+
|
|
79
|
+
# 1 = open, 0 = solid
|
|
80
|
+
volume = np.ones((3, 3, 3), dtype=np.uint8)
|
|
81
|
+
|
|
82
|
+
# adds an obstruction
|
|
83
|
+
volume[1, 1, 1] = 0
|
|
84
|
+
|
|
85
|
+
# calculate pairwise voxel visibility
|
|
86
|
+
coords, edges = seevol.visible_pairs(volume)
|
|
87
|
+
|
|
88
|
+
print(f"{len(coords)} open voxels containing {len(edges)} visible pairs")
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
It prints the number of open voxels and visible pairs:
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
26 open voxels containing 214 visible pairs
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Using .npy files
|
|
98
|
+
|
|
99
|
+
> [!IMPORTANT]
|
|
100
|
+
> NumPy array volumes as .npy files must be 3D and contain binary values, with 0 = solid and 1 = open voxels.
|
|
101
|
+
|
|
102
|
+
To import your own 3D NumPy volume and calculate visibility as with the above example:
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
import numpy as np
|
|
106
|
+
import seevol
|
|
107
|
+
|
|
108
|
+
# load your own 3D NumPy volume: 0 = solid, 1 = open
|
|
109
|
+
volume = np.load("my_volume.npy")
|
|
110
|
+
|
|
111
|
+
# calculate pairwise voxel visibility
|
|
112
|
+
coords, edges = seevol.visible_pairs(volume)
|
|
113
|
+
|
|
114
|
+
print(f"{len(coords)} open voxels containing {len(edges)} visible pairs")
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Visualisation examples on large artificial volumes
|
|
118
|
+
|
|
119
|
+
Below shows an example output of the visibility score per voxel as a heatmap within an intricate 150x150x150 volume, generated using `tunnel_excavator()` to mimic an ant-nest structure and visualised with Pyvista using `visibility_visualiser()`:
|
|
120
|
+
|
|
121
|
+

|
|
122
|
+
|
|
123
|
+
A visibility scored volume using a generated `porespy` volume:
|
|
124
|
+
|
|
125
|
+

|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
## References
|
|
129
|
+
|
|
130
|
+
> 1. Amanatides, J., & Woo, A. (1987, August). A fast voxel traversal algorithm for ray tracing. In Eurographics (Vol. 87, No. 3, pp. 3-10).
|
|
131
|
+
|
|
132
|
+
|
seevol-0.1.0/README.md
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# seeVol
|
|
2
|
+
|
|
3
|
+
<p align="center">
|
|
4
|
+
<img src="images/readme_banner.png" alt="3D visualisation of an artificial ant nest volume coloured by voxel visibility" width="800">
|
|
5
|
+
</p>
|
|
6
|
+
|
|
7
|
+
`seevol` analyses line-of-sight visibility in 3D binary volumes, where 0 represents a solid/opaque voxel, and 1 represents open/transparent voxels.
|
|
8
|
+
|
|
9
|
+
The core functionality is based on the Amanatides & Woo voxel traversal algorithm <sup>1</sup> and is implemented in C++ to compute straight line visibility between all pairs of open (non solid) voxels in a given volume.
|
|
10
|
+
|
|
11
|
+
This tool tests visibility between **all** open pairs throughout the volume, which permits per-voxel visibility analysis metrics to be calculated downstream for every open voxel.
|
|
12
|
+
|
|
13
|
+
> [!WARNING]
|
|
14
|
+
> `seevol` computes all-pairs visibility, which scales as O(open_voxels<sup>2</sup>). This becomes slow or memory-limiting beyond a few hundred thousand open voxels — see [Limitations](#limitations) before running on large volumes. Downsampling the volume is likely to help but will impact results.
|
|
15
|
+
|
|
16
|
+
## Dependencies
|
|
17
|
+
|
|
18
|
+
A C++17 or greater build toolchain is required for source installation, alongside Python >=3.10.
|
|
19
|
+
|
|
20
|
+
Pyvista is a dependency for visualisations of the visibility metrics output onto the 3D volume itself (see [this visual example](examples/visualiser_example.py) for use).
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
To install seevol:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pip install seevol
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
or for uv users:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
uv pip install seevol
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Usage
|
|
37
|
+
|
|
38
|
+
The core function `visible_pairs()` accepts a 3D binary (0 = solid, 1 = open) NumPy array as input and outputs two NumPy arrays:
|
|
39
|
+
|
|
40
|
+
- `coords` — shape (N, 3), the open voxel coordinates in the volume
|
|
41
|
+
- `edges` — shape (M, 2), pairs of indices into `coords` for voxels that are mutually visible
|
|
42
|
+
|
|
43
|
+
For example, if `edges[0]` is `[2, 7]`,the visibility edge connects `coords[2]` and `coords[7]` and these two voxels are visible to each other via direct line of sight.
|
|
44
|
+
|
|
45
|
+
Users can load .npy files in Python using `np.load()` and pass the result to `visible_pairs()`.
|
|
46
|
+
|
|
47
|
+
Downstream analyses or visualisations are then possible from this output, such as visualising a 'visibility heatmap' using Pyvista (see below)
|
|
48
|
+
|
|
49
|
+
## Limitations
|
|
50
|
+
|
|
51
|
+
This tool currently uses all-pair calculation using voxel ray traversal. Because of this, the ray traversal algorithm scales as O(open_voxels<sup>2</sup>) and is therefore quickly unusable for large volumes.
|
|
52
|
+
|
|
53
|
+
A hard limit is imposed in the C++ source file which limits open voxels to 1,000,000. In reality, volumes with over 500,000 open voxels are likely to significant memory issues as very large visible edges will be stored. Methods to reduce this runtime will hopefully be introduced over time.
|
|
54
|
+
|
|
55
|
+
## Minimal example
|
|
56
|
+
|
|
57
|
+
The [quickstart example](examples/quickstart.py) creates a small volume of open voxels with one solid voxel blocking visibility:
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
import numpy as np
|
|
61
|
+
import seevol
|
|
62
|
+
|
|
63
|
+
# 1 = open, 0 = solid
|
|
64
|
+
volume = np.ones((3, 3, 3), dtype=np.uint8)
|
|
65
|
+
|
|
66
|
+
# adds an obstruction
|
|
67
|
+
volume[1, 1, 1] = 0
|
|
68
|
+
|
|
69
|
+
# calculate pairwise voxel visibility
|
|
70
|
+
coords, edges = seevol.visible_pairs(volume)
|
|
71
|
+
|
|
72
|
+
print(f"{len(coords)} open voxels containing {len(edges)} visible pairs")
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
It prints the number of open voxels and visible pairs:
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
26 open voxels containing 214 visible pairs
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Using .npy files
|
|
82
|
+
|
|
83
|
+
> [!IMPORTANT]
|
|
84
|
+
> NumPy array volumes as .npy files must be 3D and contain binary values, with 0 = solid and 1 = open voxels.
|
|
85
|
+
|
|
86
|
+
To import your own 3D NumPy volume and calculate visibility as with the above example:
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
import numpy as np
|
|
90
|
+
import seevol
|
|
91
|
+
|
|
92
|
+
# load your own 3D NumPy volume: 0 = solid, 1 = open
|
|
93
|
+
volume = np.load("my_volume.npy")
|
|
94
|
+
|
|
95
|
+
# calculate pairwise voxel visibility
|
|
96
|
+
coords, edges = seevol.visible_pairs(volume)
|
|
97
|
+
|
|
98
|
+
print(f"{len(coords)} open voxels containing {len(edges)} visible pairs")
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Visualisation examples on large artificial volumes
|
|
102
|
+
|
|
103
|
+
Below shows an example output of the visibility score per voxel as a heatmap within an intricate 150x150x150 volume, generated using `tunnel_excavator()` to mimic an ant-nest structure and visualised with Pyvista using `visibility_visualiser()`:
|
|
104
|
+
|
|
105
|
+

|
|
106
|
+
|
|
107
|
+
A visibility scored volume using a generated `porespy` volume:
|
|
108
|
+
|
|
109
|
+

|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
## References
|
|
113
|
+
|
|
114
|
+
> 1. Amanatides, J., & Woo, A. (1987, August). A fast voxel traversal algorithm for ray tracing. In Eurographics (Vol. 87, No. 3, pp. 3-10).
|
|
115
|
+
|
|
116
|
+
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# quickstart.py - minimal python example of seevol
|
|
2
|
+
# date: 02-11-2026
|
|
3
|
+
# author: Archie Benn sj19031@bristol.ac.uk
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
import seevol
|
|
7
|
+
|
|
8
|
+
# 1 = open, 0 = solid
|
|
9
|
+
volume = np.ones((3, 3, 3), dtype=np.uint8)
|
|
10
|
+
|
|
11
|
+
# adds an obstruction
|
|
12
|
+
volume[1, 1, 1] = 0
|
|
13
|
+
|
|
14
|
+
# calculate pairwise voxel visibility
|
|
15
|
+
coords, edges = seevol.visible_pairs(volume)
|
|
16
|
+
|
|
17
|
+
print(f"{len(coords)} open voxels containing {len(edges)} visible pairs")
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# example.py - imports functions to view visibility scored in 3D excavated space
|
|
2
|
+
# date: 02-11-2026
|
|
3
|
+
# author: Archie Benn sj19031@bristol.ac.uk
|
|
4
|
+
|
|
5
|
+
import seevol
|
|
6
|
+
|
|
7
|
+
# create example NumPy volume with excavator
|
|
8
|
+
example_nest = seevol.tunnel_excavator(size=180, seed=14, n_steps=18000, branch_prob=1e-5, radius=1)
|
|
9
|
+
|
|
10
|
+
# view visibility in pyvista window
|
|
11
|
+
seevol.visibility_visualiser(example_nest, verbose=True)
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["scikit-build-core", "pybind11"]
|
|
3
|
+
build-backend = "scikit_build_core.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "seevol"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
authors = [
|
|
9
|
+
{ name="Archie Benn", email="sj19031@bristol.ac.uk" },
|
|
10
|
+
]
|
|
11
|
+
description = "All-pairs visibility analysis on voxel volumes (Amanatides & Woo, C++)"
|
|
12
|
+
readme = "README.md"
|
|
13
|
+
license = "MIT"
|
|
14
|
+
license-files = ["LICENSE"]
|
|
15
|
+
requires-python = ">=3.10"
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Programming Language :: C++",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Operating System :: OS Independent",
|
|
20
|
+
]
|
|
21
|
+
dependencies = ["numpy", "pyvista"]
|
|
22
|
+
|
|
23
|
+
[project.urls]
|
|
24
|
+
Repository = "https://github.com/archiebenn/seeVol"
|
|
25
|
+
|
|
26
|
+
[tool.scikit-build]
|
|
27
|
+
wheel.packages = ["src/seevol"]
|
|
28
|
+
sdist.exclude = ["tests", "development_log.md", "uv-cheatsheet.md"]
|
|
29
|
+
|
|
30
|
+
[tool.pytest.ini_options]
|
|
31
|
+
testpaths = ["tests"]
|
|
32
|
+
|
|
33
|
+
[dependency-groups]
|
|
34
|
+
dev = [
|
|
35
|
+
"pytest>=9.2.0",
|
|
36
|
+
]
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from .generator import tunnel_excavator
|
|
2
|
+
from .voxel_occlusion import get_voxel_value, is_visible, visible_pairs
|
|
3
|
+
from .visualiser import visibility_visualiser
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"get_voxel_value",
|
|
7
|
+
"is_visible",
|
|
8
|
+
"tunnel_excavator",
|
|
9
|
+
"visible_pairs",
|
|
10
|
+
"visibility_visualiser",
|
|
11
|
+
]
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# generator.py - generates example volumes to simulate ant nest excavations and intricate structures
|
|
2
|
+
# date: 28-10-2026
|
|
3
|
+
# author: Archie Benn sj19031@bristol.ac.uk
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
import random
|
|
7
|
+
|
|
8
|
+
def tunnel_excavator(size = 100, n_steps = 20000, branch_prob = 1e-5, radius = 1, seed=0):
|
|
9
|
+
"""
|
|
10
|
+
Creates empty grid volume (default volume 100^3 voxels).
|
|
11
|
+
|
|
12
|
+
Produces a volume with fully-connected and randomly carved tunnels within that grid volume.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
rng = random.Random(seed)
|
|
16
|
+
|
|
17
|
+
# fill size^3 volume with 0s (represents solid/non-open e.g soil within an excavated ant nest)
|
|
18
|
+
vol = np.zeros((size,size,size), dtype=np.uint8)
|
|
19
|
+
|
|
20
|
+
def excavate(position):
|
|
21
|
+
"""
|
|
22
|
+
Takes current position and excavates by the radius provided (radius = 1 -> 3x3 excavation).
|
|
23
|
+
|
|
24
|
+
Sets 'excavated' voxel cells to 1.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
# get current coordinates of 'position'
|
|
28
|
+
x,y,z = position
|
|
29
|
+
# define low and high positions for carving given radius
|
|
30
|
+
low = [max(0, x-radius), max(0, y-radius), max(0, z-radius)]
|
|
31
|
+
high = [min(size, x + radius + 1), min(size, y + radius + 1), min(size, z + radius + 1)]
|
|
32
|
+
|
|
33
|
+
# ****actual excavation step i.e. set carved voxel(s) == 1****
|
|
34
|
+
vol[low[0]:high[0], low[1]:high[1], low[2]:high[2]] = 1
|
|
35
|
+
|
|
36
|
+
# begin 'excavation' with starting position near top in centre (1 down in y plane)
|
|
37
|
+
start = (size //2, size-1, size//2)
|
|
38
|
+
excavate(start)
|
|
39
|
+
|
|
40
|
+
# active walker list (e.g an ant excavating)
|
|
41
|
+
heads = [start]
|
|
42
|
+
|
|
43
|
+
# apply a slight y and x bias for increased chance of moving down/sideways (more ant-like)
|
|
44
|
+
# change these values in each axes to adjust excavation shapes
|
|
45
|
+
moves = (
|
|
46
|
+
[(1,0,0)] * 21 + [(-1,0,0)] * 21 + # x
|
|
47
|
+
[(0,1,0)] * 20 + [(0,-1,0)] * 22 + # y
|
|
48
|
+
[(0,0,1)] * 20 + [(0,0,-1)] * 20 # z
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
for _ in range(n_steps):
|
|
52
|
+
|
|
53
|
+
head_idx = rng.randrange(len(heads))
|
|
54
|
+
x,y,z = heads[head_idx]
|
|
55
|
+
|
|
56
|
+
# movement selector
|
|
57
|
+
dx,dy,dz = rng.choice(moves)
|
|
58
|
+
|
|
59
|
+
# new head coordinates after move
|
|
60
|
+
nx,ny,nz = x + dx, y + dy, z + dz
|
|
61
|
+
|
|
62
|
+
# only excavates if the new coordinates are within size of voluem
|
|
63
|
+
# i.e if new position does not fall outside the defined volume zone 'size'
|
|
64
|
+
if 0 <= nx < size and 0 <= ny < size and 0 <= nz < size:
|
|
65
|
+
|
|
66
|
+
# excavate and move head/ant
|
|
67
|
+
excavate((nx,ny,nz))
|
|
68
|
+
heads[head_idx] = (nx,ny,nz)
|
|
69
|
+
|
|
70
|
+
# occasionally spawn new branch form current position
|
|
71
|
+
if rng.random() < branch_prob:
|
|
72
|
+
heads.append((nx,ny,nz))
|
|
73
|
+
|
|
74
|
+
return vol
|