Add mdbook
119
book/src/tutorials/city-builder.md
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
# City Builder
|
||||
|
||||
In this tutorial we'll create a dense grid of buildings, then cut away from them to place roads with curves. We'll also make use of a [generator](../api/advanced-scripting/generators.md) to combine the buildings with the roads.
|
||||
|
||||

|
||||
|
||||
## Setting Up
|
||||
Create a Bezier Curve object. You can enter edit mode and delete the default curve it creates.
|
||||
|
||||
Then create a new script. Setting up an [external editor](../setup/external-editing.md) is recommended.
|
||||
|
||||
Import Geometry Script, and create a basic tree builder function. We'll add a few arguments to configure the buildings.
|
||||
|
||||
```python
|
||||
from geometry_script import *
|
||||
|
||||
@tree("City Builder")
|
||||
def city_builder(
|
||||
geometry: Geometry,
|
||||
seed: Int,
|
||||
road_width: Float = 0.25,
|
||||
size_x: Float = 5, size_y: Float = 5, density: Float = 10,
|
||||
building_size_min: Vector = (0.1, 0.1, 0.2),
|
||||
building_size_max: Vector = (0.3, 0.3, 1),
|
||||
):
|
||||
return geometry
|
||||
```
|
||||
|
||||
Run the script to create the tree, then add a *Geometry Nodes* modifier to your curve object and select the *City Builger* node group.
|
||||
|
||||
## Buildings
|
||||
Let's start with the buildings. We'll distribute points on a grid with `size_x` and `size_y`.
|
||||
|
||||
```python
|
||||
def city_builder(...):
|
||||
building_points = grid(size_x=size_x, size_y=size_y).distribute_points_on_faces(density=density, seed=seed).points
|
||||
return building_points
|
||||
```
|
||||
|
||||
Next, we'll instance cubes on these points to serve as our buildings. We move the cube object up half its height so the buildings sit flat on the grid, and scale them randomly between the min and max sizes.
|
||||
|
||||
```python
|
||||
def city_builder(...):
|
||||
...
|
||||
return building_points.instance_on_points(
|
||||
instance=cube().transform(translation=(0, 0, 0.5)),
|
||||
scale=random_value(data_type='FLOAT_VECTOR', min=building_size_min, max=building_size_max, seed=seed),
|
||||
)
|
||||
```
|
||||
|
||||
## Roads
|
||||
Using `curve_to_mesh`, we can turn the input curve into a flat mesh. We'll use the `yield` keyword to join the curve mesh and the building mesh automatically. Change the `building_points.instance_on_points` line to use `yield` for this to work.
|
||||
|
||||
```python
|
||||
def city_builder(...):
|
||||
yield geometry.curve_to_mesh(profile_curve=curve_line(
|
||||
start=combine_xyz(x=road_width * -0.5),
|
||||
end=combine_xyz(x=road_width * 0.5)
|
||||
))
|
||||
...
|
||||
yield building_points.instance_on_points(...)
|
||||
```
|
||||
|
||||
But now the buildings are overlapping the road. We need to remove any point that falls within the road curve. We'll use `geometry_proximity` and `delete_geometry` to find and remove these invalid points.
|
||||
|
||||
```python
|
||||
def city_builder(...):
|
||||
...
|
||||
building_points = ...
|
||||
road_points = geometry.curve_to_points(mode='EVALUATED').points
|
||||
building_points = building_points.delete_geometry(
|
||||
domain='POINT',
|
||||
selection=geometry_proximity(target_element='POINTS', target=road_points, source_position=position()).distance < road_width
|
||||
)
|
||||
...
|
||||
```
|
||||
|
||||
## Drawing Roads
|
||||
Enter edit mode and select the *Draw* tool. Simply draw roads onto your city to see the buildings and meshes update.
|
||||
|
||||

|
||||
|
||||
## Final Script
|
||||
|
||||
```python
|
||||
from geometry_script import *
|
||||
|
||||
@tree("City Builder")
|
||||
def city_builder(
|
||||
geometry: Geometry,
|
||||
seed: Int,
|
||||
road_width: Float = 0.25,
|
||||
size_x: Float = 5, size_y: Float = 5, density: Float = 10,
|
||||
building_size_min: Vector = (0.1, 0.1, 0.2),
|
||||
building_size_max: Vector = (0.3, 0.3, 1),
|
||||
):
|
||||
# Road mesh
|
||||
yield geometry.curve_to_mesh(profile_curve=curve_line(
|
||||
start=combine_xyz(x=road_width * -0.5),
|
||||
end=combine_xyz(x=road_width * 0.5)
|
||||
))
|
||||
# Building points
|
||||
building_points = grid(size_x=size_x, size_y=size_y).distribute_points_on_faces(density=density, seed=seed).points
|
||||
road_points = geometry.curve_to_points(mode='EVALUATED').points
|
||||
# Delete points within the curve
|
||||
building_points = building_points.delete_geometry(
|
||||
domain='POINT',
|
||||
selection=geometry_proximity(target_element='POINTS', target=road_points, source_position=position()).distance < road_width
|
||||
)
|
||||
# Building instances
|
||||
yield building_points.instance_on_points(
|
||||
instance=cube().transform(translation=(0, 0, 0.5)),
|
||||
scale=random_value(data_type='FLOAT_VECTOR', min=building_size_min, max=building_size_max, seed=seed),
|
||||
)
|
||||
```
|
||||
|
||||
## Generated Node Tree
|
||||
|
||||

|
||||
BIN
book/src/tutorials/city_builder.gif
Normal file
|
After Width: | Height: | Size: 15 MiB |
BIN
book/src/tutorials/city_builder_nodes.png
Normal file
|
After Width: | Height: | Size: 419 KiB |
BIN
book/src/tutorials/monkey.png
Normal file
|
After Width: | Height: | Size: 292 KiB |
BIN
book/src/tutorials/monkey_points.png
Normal file
|
After Width: | Height: | Size: 577 KiB |
BIN
book/src/tutorials/monkey_volume.png
Normal file
|
After Width: | Height: | Size: 392 KiB |
BIN
book/src/tutorials/monkey_voxels.png
Normal file
|
After Width: | Height: | Size: 317 KiB |
109
book/src/tutorials/voxelize.md
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
# Voxelize
|
||||
|
||||
This tutorial walks you through creating a script that turns any mesh into voxels.
|
||||
|
||||
> This tutorial requires Blender 3.4+ for the *Distribute Points In Volume* node.
|
||||
|
||||
## Setting Up
|
||||
Create a base mesh. I'll be using a Monkey primitive.
|
||||
|
||||

|
||||
|
||||
Next, create a new script. Setting up an [external editor](../setup/external-editing.md) is recommended.
|
||||
|
||||
Import Geometry Script, and create a basic tree builder function. We'll add a `geometry` argument and annotate it with the `Geometry` type to receive our base mesh (in this case, a monkey).
|
||||
|
||||
```python
|
||||
from geometry_script import *
|
||||
|
||||
@tree("Voxelize")
|
||||
def voxelize(geometry: Geometry):
|
||||
return geometry
|
||||
```
|
||||
|
||||
Run the script to create the tree, then add a *Geometry Nodes* modifier to your mesh and select the *Voxelize* node group.
|
||||
|
||||

|
||||
|
||||
## Arguments
|
||||
Add a new argument `resolution: Float`. Give it a default value of `0.2`. This value will be used throughout the script to configure spacing and voxel density.
|
||||
|
||||
```python
|
||||
def voxelize(geometry: Geometry, resolution: Float = 0.2):
|
||||
...
|
||||
```
|
||||
|
||||
## Mesh to Volume
|
||||
We want to convert the mesh to a hollow volume, so only the outside of the mesh has voxel instances. This will improve the performance of our script.
|
||||
|
||||
Use the `mesh_to_volume` function on the base mesh to convert it to a volume.
|
||||
|
||||
```python
|
||||
def voxelize(geometry: Geometry, resolution: Float = 0.2):
|
||||
return geometry.mesh_to_volume( # Hollow mesh volume
|
||||
interior_band_width=resolution,
|
||||
fill_volume=False
|
||||
)
|
||||
```
|
||||
|
||||

|
||||
|
||||
## Volume to Points
|
||||
Next, we need to create points to instance each voxel cube on. Use `distribute_points_in_volume` with the mode set to `DENSITY_GRID` to create a uniform distribution of points.
|
||||
|
||||
```python
|
||||
def voxelize(geometry: Geometry, resolution: Float = 0.2):
|
||||
return geometry.mesh_to_volume(
|
||||
interior_band_width=resolution,
|
||||
fill_volume=False
|
||||
).distribute_points_in_volume( # Uniform grid distribution
|
||||
mode='DENSITY_GRID',
|
||||
spacing=resolution
|
||||
)
|
||||
```
|
||||
|
||||

|
||||
|
||||
## Instance Cubes
|
||||
Finally, use `instance_on_points` with a cube of size `resolution` to instance a cube on each point created from our mesh.
|
||||
|
||||
```python
|
||||
def voxelize(geometry: Geometry, resolution: Float = 0.2):
|
||||
return geometry.mesh_to_volume(
|
||||
interior_band_width=resolution,
|
||||
fill_volume=False
|
||||
).distribute_points_in_volume(
|
||||
mode='DENSITY_GRID',
|
||||
spacing=resolution
|
||||
).instance_on_points( # Cube instancing
|
||||
instance=cube(size=resolution)
|
||||
)
|
||||
```
|
||||
|
||||

|
||||
|
||||
You can lower the resolution to get smaller, more detailed voxels, or raise it to get larger voxels.
|
||||
|
||||
## Final Script
|
||||
|
||||
```python
|
||||
# NOTE: This example requires Blender 3.4+
|
||||
|
||||
from geometry_script import *
|
||||
|
||||
@tree("Voxelize")
|
||||
def voxelize(geometry: Geometry, resolution: Float = 0.2):
|
||||
return geometry.mesh_to_volume(
|
||||
interior_band_width=resolution,
|
||||
fill_volume=False
|
||||
).distribute_points_in_volume(
|
||||
mode='DENSITY_GRID',
|
||||
spacing=resolution
|
||||
).instance_on_points(
|
||||
instance=cube(size=resolution)
|
||||
)
|
||||
```
|
||||
|
||||
## Generated Node Tree
|
||||
|
||||

|
||||
BIN
book/src/tutorials/voxelize_modifier.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
book/src/tutorials/voxelize_nodes.png
Normal file
|
After Width: | Height: | Size: 170 KiB |