Skip to content

Commit a681059

Browse files
committed
2 parents 742a37b + 34bf5a9 commit a681059

10 files changed

Lines changed: 1532 additions & 27 deletions

File tree

debris_flow.mp4

705 KB
Binary file not shown.

pydebflow.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,15 @@ def cmd_simulate(args):
3737
visualize=not args.no_viz
3838
)
3939
elif args.dem:
40+
# Parse polygon vertices if provided
41+
release_vertices = None
42+
if hasattr(args, 'release_polygon') and args.release_polygon:
43+
coords = [int(x.strip()) for x in args.release_polygon.split(',')]
44+
if len(coords) % 2 != 0:
45+
print("Error: --release-polygon must have even number of values")
46+
sys.exit(1)
47+
release_vertices = [(coords[i], coords[i+1]) for i in range(0, len(coords), 2)]
48+
4049
run_dem_simulation(
4150
dem_file=args.dem,
4251
output_dir=args.output,
@@ -45,6 +54,7 @@ def cmd_simulate(args):
4554
release_j=args.release_col,
4655
release_radius=args.release_radius,
4756
release_height=args.release_height,
57+
release_vertices=release_vertices,
4858
animate_3d=args.animate and not args.no_viz,
4959
export_video=args.video
5060
)
@@ -162,6 +172,9 @@ def main():
162172
help='Release zone radius in cells (default: 10)')
163173
sim_release.add_argument('--release-height', type=float, default=5.0,
164174
help='Release zone height in meters (default: 5.0)')
175+
sim_release.add_argument('--release-polygon', type=str, metavar='VERTICES',
176+
help='Polygon release zone as comma-separated row,col pairs '
177+
'(e.g. "10,20,10,40,30,40,30,20")')
165178

166179
sim_viz = sim_parser.add_argument_group('Visualization')
167180
sim_viz.add_argument('--animate', '-a', action='store_true',

run_simulation.py

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,7 @@ def run_dem_simulation(dem_file: str,
272272
release_j: int = None,
273273
release_radius: int = 10,
274274
release_height: float = 5.0,
275+
release_vertices: list = None,
275276
animate_3d: bool = True,
276277
export_video: bool = False) -> None:
277278
"""
@@ -284,6 +285,7 @@ def run_dem_simulation(dem_file: str,
284285
release_i, release_j: Release zone center (auto if None)
285286
release_radius: Release zone radius in cells
286287
release_height: Initial release height (m)
288+
release_vertices: List of (row, col) tuples for polygon release zone
287289
animate_3d: Show 3D animation
288290
export_video: Export animation to MP4
289291
"""
@@ -298,12 +300,6 @@ def run_dem_simulation(dem_file: str,
298300
print(f" Cell size: {terrain.cell_size} m")
299301
print(f" Elevation range: {terrain.elevation.min():.1f} to {terrain.elevation.max():.1f} m")
300302

301-
# Auto-detect release zone if not specified
302-
if release_i is None:
303-
release_i = terrain.rows // 5 # Upper portion
304-
if release_j is None:
305-
release_j = terrain.cols // 2 # Center
306-
307303
# Setup model
308304
print("\n[2/6] Configuring flow model...")
309305
params = FlowParameters(
@@ -319,9 +315,26 @@ def run_dem_simulation(dem_file: str,
319315
solver = NOCTVDSolver(terrain, model, solver_config)
320316

321317
# Initialize release
322-
print(f"\n[3/6] Creating release zone at ({release_i}, {release_j})...")
318+
print(f"\n[3/6] Creating release zone...")
323319
state = FlowState.zeros((terrain.rows, terrain.cols))
324-
release = terrain.create_release_zone(release_i, release_j, release_radius, release_height)
320+
321+
if release_vertices is not None and len(release_vertices) >= 3:
322+
# Polygon release zone
323+
release = terrain.create_polygon_release_zone(
324+
vertices=release_vertices,
325+
height=release_height,
326+
smooth=True
327+
)
328+
print(f" Polygon release with {len(release_vertices)} vertices")
329+
else:
330+
# Circular release zone (original behavior)
331+
if release_i is None:
332+
release_i = terrain.rows // 5
333+
if release_j is None:
334+
release_j = terrain.cols // 2
335+
release = terrain.create_release_zone(release_i, release_j, release_radius, release_height)
336+
print(f" Circular release at ({release_i}, {release_j}), radius={release_radius}")
337+
325338
state.h_solid = release * 0.7
326339
state.h_fluid = release * 0.3
327340

@@ -457,7 +470,10 @@ def main():
457470
release_group.add_argument('--release-radius', type=int, default=10,
458471
help='Release zone radius in cells (default: 10)')
459472
release_group.add_argument('--release-height', type=float, default=5.0,
460-
help='Release zone height in meters (default: 5.0)')
473+
help='Release zone height in meters (default: 5.0)')
474+
release_group.add_argument('--release-polygon', type=str, metavar='VERTICES',
475+
help='Polygon release zone as comma-separated row,col pairs '
476+
'(e.g. "10,20,10,40,30,40,30,20")')
461477

462478
# Visualization
463479
viz_group = parser.add_argument_group('Visualization')
@@ -485,6 +501,18 @@ def main():
485501

486502
# DEM simulation
487503
if args.dem_file:
504+
# Parse polygon vertices if provided
505+
release_vertices = None
506+
if args.release_polygon:
507+
coords = [int(x.strip()) for x in args.release_polygon.split(',')]
508+
if len(coords) % 2 != 0:
509+
print("Error: --release-polygon must have even number of values (row,col pairs)")
510+
sys.exit(1)
511+
release_vertices = [(coords[i], coords[i+1]) for i in range(0, len(coords), 2)]
512+
if len(release_vertices) < 3:
513+
print("Error: --release-polygon requires at least 3 vertex pairs")
514+
sys.exit(1)
515+
488516
run_dem_simulation(
489517
dem_file=args.dem_file,
490518
output_dir=args.output_dir,
@@ -493,6 +521,7 @@ def main():
493521
release_j=args.release_col,
494522
release_radius=args.release_radius,
495523
release_height=args.release_height,
524+
release_vertices=release_vertices,
496525
animate_3d=args.animate_3d and not args.no_viz,
497526
export_video=args.export_video
498527
)

src/core/terrain.py

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,83 @@ def create_release_zone(self, center_i: int, center_j: int,
8484

8585
return release
8686

87+
def create_polygon_release_zone(self, vertices: list, height: float,
88+
smooth: bool = True) -> np.ndarray:
89+
"""
90+
Create a release zone from a polygon defined by vertices.
91+
92+
Args:
93+
vertices: List of (row, col) tuples defining the polygon boundary.
94+
At least 3 vertices required.
95+
height: Maximum release height in meters.
96+
smooth: If True, apply parabolic falloff from centroid.
97+
If False, uniform height within polygon.
98+
99+
Returns:
100+
Release height array with shape (rows, cols).
101+
"""
102+
if len(vertices) < 3:
103+
raise ValueError("Polygon requires at least 3 vertices")
104+
105+
from matplotlib.path import Path as MplPath
106+
107+
# Create grid of all cell coordinates
108+
rows_idx, cols_idx = np.mgrid[0:self.rows, 0:self.cols]
109+
points = np.column_stack([rows_idx.ravel(), cols_idx.ravel()])
110+
111+
# Create polygon path and test containment
112+
poly_path = MplPath(vertices)
113+
mask = poly_path.contains_points(points).reshape(self.rows, self.cols)
114+
115+
return self.create_mask_release_zone(mask, height, smooth=smooth)
116+
117+
def create_mask_release_zone(self, mask: np.ndarray, height: float,
118+
smooth: bool = True) -> np.ndarray:
119+
"""
120+
Create a release zone from a boolean mask.
121+
122+
Args:
123+
mask: Boolean array of shape (rows, cols). True = release zone.
124+
height: Maximum release height in meters.
125+
smooth: If True, apply parabolic falloff from centroid.
126+
If False, uniform height within mask.
127+
128+
Returns:
129+
Release height array with shape (rows, cols).
130+
"""
131+
if mask.shape != (self.rows, self.cols):
132+
raise ValueError(
133+
f"Mask shape {mask.shape} doesn't match terrain ({self.rows}, {self.cols})"
134+
)
135+
136+
release = np.zeros_like(self.elevation)
137+
138+
if not mask.any():
139+
return release
140+
141+
if smooth:
142+
# Compute centroid of masked region
143+
masked_coords = np.argwhere(mask)
144+
centroid_i = masked_coords[:, 0].mean()
145+
centroid_j = masked_coords[:, 1].mean()
146+
147+
# Max distance from centroid to any masked cell
148+
dists = np.sqrt(
149+
(masked_coords[:, 0] - centroid_i)**2 +
150+
(masked_coords[:, 1] - centroid_j)**2
151+
)
152+
max_dist = dists.max() if dists.max() > 0 else 1.0
153+
154+
# Apply parabolic falloff
155+
for idx in range(len(masked_coords)):
156+
i, j = masked_coords[idx]
157+
d = dists[idx]
158+
release[i, j] = height * (1 - (d / max_dist)**2)
159+
else:
160+
release[mask] = height
161+
162+
return release
163+
87164
@classmethod
88165
def from_geotiff(cls, filepath: str) -> 'Terrain':
89166
"""Load terrain from GeoTIFF file."""

0 commit comments

Comments
 (0)