Сохраняемся
This commit is contained in:
+135
-35
@@ -81,42 +81,103 @@ def _height_at(
|
||||
def build_seafloor_params(
|
||||
seed: int = 42,
|
||||
*,
|
||||
size_x: float = 40.0,
|
||||
size_y: float = 60.0,
|
||||
size_x: float | None = None,
|
||||
size_y: float | None = None,
|
||||
relief_scale_pct: float = 20.0,
|
||||
corridor: dict[str, Any] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Build seafloor heightfield params.
|
||||
|
||||
When ``corridor`` is provided (AUV survey swath), random unevenness is placed
|
||||
only inside that yellow echosounder strip. Feature size scales with
|
||||
``relief_scale_pct`` percent of the strip width.
|
||||
"""
|
||||
rng = random.Random(int(seed))
|
||||
size_x = max(4.0, float(size_x))
|
||||
size_y = max(4.0, float(size_y))
|
||||
relief_scale_pct = max(1.0, min(100.0, float(relief_scale_pct)))
|
||||
|
||||
corr = corridor if isinstance(corridor, dict) else {}
|
||||
corr = {k: v for k, v in corr.items() if v is not None}
|
||||
auv_x = float(corr.get("auvX", 0.0))
|
||||
auv_y = float(corr.get("auvY", 0.0))
|
||||
heading_deg = float(corr.get("headingDeg", 0.0))
|
||||
survey_length = max(1.0, float(corr.get("surveyLength", 40.0)))
|
||||
auv_depth = max(0.3, float(corr.get("auvDepth", 2.5)))
|
||||
swath_deg = max(5.0, min(170.0, float(corr.get("swathAngleDeg", 90.0))))
|
||||
|
||||
half_swath = auv_depth * math.tan(math.radians(swath_deg) * 0.5)
|
||||
strip_width = max(4.0, 2.0 * half_swath)
|
||||
half_w = strip_width * 0.5
|
||||
heading = math.radians(heading_deg)
|
||||
hx, hy = math.cos(heading), math.sin(heading)
|
||||
nx, ny = -math.sin(heading), math.cos(heading)
|
||||
|
||||
# AABB of the survey strip (+ padding) → terrain extent around origin.
|
||||
pad = strip_width * 0.6 + 8.0
|
||||
xs: list[float] = []
|
||||
ys: list[float] = []
|
||||
for t in (0.0, survey_length):
|
||||
for lat in (-half_w, half_w):
|
||||
xs.append(auv_x + hx * t + nx * lat)
|
||||
ys.append(auv_y + hy * t + ny * lat)
|
||||
reach = max(
|
||||
max(abs(v) for v in xs) + pad,
|
||||
max(abs(v) for v in ys) + pad,
|
||||
strip_width + 12.0,
|
||||
survey_length * 0.35 + 12.0,
|
||||
)
|
||||
auto_size = max(24.0, reach * 2.0)
|
||||
size_x = max(4.0, float(size_x) if size_x is not None else auto_size)
|
||||
size_y = max(4.0, float(size_y) if size_y is not None else auto_size)
|
||||
|
||||
# Mild background undulation (not the main corridor features).
|
||||
base_z = rng.uniform(-8.0, -3.0)
|
||||
amplitude = rng.uniform(0.15, 0.6)
|
||||
frequency = rng.uniform(0.15, 0.55)
|
||||
hills = [
|
||||
(
|
||||
rng.uniform(-size_x * 0.4, size_x * 0.4),
|
||||
rng.uniform(-size_y * 0.4, size_y * 0.4),
|
||||
rng.uniform(0.3, 1.4),
|
||||
rng.uniform(2.0, 8.0),
|
||||
amplitude = rng.uniform(0.05, 0.18)
|
||||
frequency = rng.uniform(0.08, 0.25)
|
||||
|
||||
# Characteristic feature size = pct of yellow strip width.
|
||||
feature_scale = strip_width * (relief_scale_pct / 100.0)
|
||||
feature_scale = max(0.15, feature_scale)
|
||||
|
||||
def point_in_strip(t: float, lat: float) -> tuple[float, float]:
|
||||
return (
|
||||
auv_x + hx * t + nx * lat,
|
||||
auv_y + hy * t + ny * lat,
|
||||
)
|
||||
for _ in range(rng.randint(2, 5))
|
||||
]
|
||||
valleys = [
|
||||
(
|
||||
rng.uniform(-size_x * 0.4, size_x * 0.4),
|
||||
rng.uniform(-size_y * 0.4, size_y * 0.4),
|
||||
rng.uniform(0.2, 0.9),
|
||||
rng.uniform(2.0, 7.0),
|
||||
)
|
||||
for _ in range(rng.randint(1, 4))
|
||||
]
|
||||
bumps = [
|
||||
(
|
||||
rng.uniform(-size_x * 0.45, size_x * 0.45),
|
||||
rng.uniform(-size_y * 0.45, size_y * 0.45),
|
||||
rng.uniform(0.05, 0.4),
|
||||
rng.uniform(0.4, 2.0),
|
||||
)
|
||||
for _ in range(rng.randint(8, 20))
|
||||
]
|
||||
|
||||
n_hills = rng.randint(2, 4)
|
||||
n_valleys = rng.randint(1, 3)
|
||||
# More bumps when features are smaller so the strip stays filled.
|
||||
density = max(0.35, min(1.6, 20.0 / max(relief_scale_pct, 1.0)))
|
||||
n_bumps = int(round(rng.uniform(10, 18) * density))
|
||||
n_bumps = max(6, min(36, n_bumps))
|
||||
|
||||
hills = []
|
||||
for _ in range(n_hills):
|
||||
t = rng.uniform(0.0, survey_length)
|
||||
lat = rng.uniform(-half_w * 0.85, half_w * 0.85)
|
||||
x, y = point_in_strip(t, lat)
|
||||
rad = feature_scale * rng.uniform(0.9, 1.8)
|
||||
amp = feature_scale * rng.uniform(0.25, 0.7)
|
||||
hills.append((x, y, amp, rad))
|
||||
|
||||
valleys = []
|
||||
for _ in range(n_valleys):
|
||||
t = rng.uniform(0.0, survey_length)
|
||||
lat = rng.uniform(-half_w * 0.85, half_w * 0.85)
|
||||
x, y = point_in_strip(t, lat)
|
||||
rad = feature_scale * rng.uniform(0.8, 1.6)
|
||||
amp = feature_scale * rng.uniform(0.18, 0.5)
|
||||
valleys.append((x, y, amp, rad))
|
||||
|
||||
bumps = []
|
||||
for _ in range(n_bumps):
|
||||
t = rng.uniform(0.0, survey_length)
|
||||
lat = rng.uniform(-half_w * 0.98, half_w * 0.98)
|
||||
x, y = point_in_strip(t, lat)
|
||||
rad = feature_scale * rng.uniform(0.35, 1.15)
|
||||
amp = feature_scale * rng.uniform(0.1, 0.4)
|
||||
bumps.append((x, y, amp, rad))
|
||||
|
||||
return {
|
||||
"seed": int(seed),
|
||||
"sizeX": size_x,
|
||||
@@ -127,6 +188,17 @@ def build_seafloor_params(
|
||||
"hills": hills,
|
||||
"valleys": valleys,
|
||||
"bumps": bumps,
|
||||
"reliefScalePct": relief_scale_pct,
|
||||
"stripWidth": strip_width,
|
||||
"corridor": {
|
||||
"auvX": auv_x,
|
||||
"auvY": auv_y,
|
||||
"headingDeg": heading_deg,
|
||||
"surveyLength": survey_length,
|
||||
"auvDepth": auv_depth,
|
||||
"swathAngleDeg": swath_deg,
|
||||
"stripWidth": strip_width,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -251,12 +323,14 @@ def save_survey_surface(
|
||||
def prepare_mle_scene(
|
||||
*,
|
||||
seed: int = 42,
|
||||
size_x: float = 40.0,
|
||||
size_y: float = 60.0,
|
||||
size_x: float | None = None,
|
||||
size_y: float | None = None,
|
||||
res_x: int = 80,
|
||||
res_y: int = 120,
|
||||
output_dir: str | Path = "mle_runs",
|
||||
settings: dict[str, Any] | None = None,
|
||||
relief_scale_pct: float = 20.0,
|
||||
corridor: dict[str, Any] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Generate seafloor for simulation; create run folder with empty survey OBJ."""
|
||||
base = resolve_mle_dir(output_dir)
|
||||
@@ -269,7 +343,33 @@ def prepare_mle_scene(
|
||||
n += 1
|
||||
run_dir.mkdir(parents=True, exist_ok=False)
|
||||
|
||||
params = build_seafloor_params(seed, size_x=size_x, size_y=size_y)
|
||||
# Prefer explicit corridor fields; fill gaps from settings snapshot.
|
||||
corr: dict[str, Any] = {}
|
||||
if isinstance(corridor, dict):
|
||||
corr.update({k: v for k, v in corridor.items() if v is not None})
|
||||
if isinstance(settings, dict):
|
||||
auv = settings.get("auv") if isinstance(settings.get("auv"), dict) else {}
|
||||
beams = settings.get("beams") if isinstance(settings.get("beams"), dict) else {}
|
||||
motion = settings.get("motion") if isinstance(settings.get("motion"), dict) else {}
|
||||
defaults_from_settings = {
|
||||
"auvX": auv.get("x", settings.get("auvX")),
|
||||
"auvY": auv.get("y", settings.get("auvY")),
|
||||
"headingDeg": auv.get("headingDeg", settings.get("auvHeadingDeg")),
|
||||
"surveyLength": motion.get("surveyLength", settings.get("surveyLength")),
|
||||
"auvDepth": auv.get("depth", settings.get("auvDepth")),
|
||||
"swathAngleDeg": beams.get("swathAngleDeg", settings.get("swathAngleDeg")),
|
||||
}
|
||||
for key, value in defaults_from_settings.items():
|
||||
if corr.get(key) is None and value is not None:
|
||||
corr[key] = value
|
||||
|
||||
params = build_seafloor_params(
|
||||
seed,
|
||||
size_x=size_x,
|
||||
size_y=size_y,
|
||||
relief_scale_pct=relief_scale_pct,
|
||||
corridor=corr,
|
||||
)
|
||||
mesh = sample_seafloor_grid(params, res_x=res_x, res_y=res_y)
|
||||
# Survey OBJ starts empty and is filled from multibeam hits during motion.
|
||||
obj_path = run_dir / "seafloor.obj"
|
||||
|
||||
Reference in New Issue
Block a user