HANSOL commited on
Commit
0f8040e
ยท
1 Parent(s): e60b579

Add PhD-level formation processes for 8 landforms with metadata and stage descriptions

Browse files
engine/ideal_landforms.py CHANGED
@@ -332,60 +332,136 @@ def create_barchan_dune(grid_size: int = 100,
332
  return elevation
333
 
334
 
335
- def create_coastal_cliff(grid_size: int = 100,
336
  cliff_height: float = 30.0,
337
- num_stacks: int = 2) -> np.ndarray:
 
338
  """
339
- ํ•ด์•ˆ ์ ˆ๋ฒฝ (Coastal Cliff) + ์‹œ์Šคํƒ
340
 
341
- Args:
342
- grid_size: ๊ทธ๋ฆฌ๋“œ ํฌ๊ธฐ
343
- cliff_height: ์ ˆ๋ฒฝ ๋†’์ด
344
- num_stacks: ์‹œ์Šคํƒ ๊ฐœ์ˆ˜
345
-
346
- Returns:
347
- elevation: ๊ณ ๋„ ๋ฐฐ์—ด
 
 
 
 
 
 
 
 
 
 
 
 
 
 
348
  """
349
  h, w = grid_size, grid_size
350
  elevation = np.zeros((h, w))
351
 
 
 
 
 
 
352
  # ๋ฐ”๋‹ค (ํ•˜๋‹จ)
353
- sea_line = int(h * 0.6)
354
- elevation[sea_line:, :] = -5.0
 
 
 
355
 
356
  # ์œก์ง€ + ์ ˆ๋ฒฝ
 
357
  for r in range(sea_line):
358
  cliff_dist = sea_line - r
359
- if cliff_dist < 5:
360
- # ์ ˆ๋ฒฝ๋ฉด
361
- elevation[r, :] = cliff_height * (cliff_dist / 5)
 
362
  else:
363
  # ํ‰ํƒ„ํ•œ ์œก์ง€
364
- elevation[r, :] = cliff_height
365
-
366
- # ํŒŒ์‹๋Œ€ (Wave-cut Platform)
367
- for r in range(sea_line, sea_line + 10):
368
- if r < h:
369
- elevation[r, :] = -2.0 + (r - sea_line) * 0.2
370
-
371
- # ์‹œ์Šคํƒ (Sea Stacks)
372
- for i in range(num_stacks):
373
- sx = w // 3 + i * (w // 3)
374
- sy = sea_line + 5 + i * 3
375
 
376
- stack_height = cliff_height * 0.7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
377
 
378
- for dr in range(-3, 4):
379
- for dc in range(-3, 4):
380
- r, c = sy + dr, sx + dc
381
- if 0 <= r < h and 0 <= c < w:
382
- dist = np.sqrt(dr**2 + dc**2)
383
- if dist < 3:
384
- elevation[r, c] = stack_height * (1 - dist / 4)
385
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
386
  return elevation
387
 
388
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
389
  # ============================================
390
  # ์• ๋‹ˆ๋ฉ”์ด์…˜์šฉ ํ˜•์„ฑ๊ณผ์ • ํ•จ์ˆ˜ (Stage-based)
391
  # stage: 0.0 (์‹œ์ž‘) ~ 1.0 (์™„์„ฑ)
@@ -446,10 +522,22 @@ def create_delta_animated(grid_size: int, stage: float,
446
 
447
 
448
  def create_alluvial_fan_animated(grid_size: int, stage: float,
449
- cone_angle: float = 90.0, max_height: float = 50.0) -> np.ndarray:
450
- """์„ ์ƒ์ง€ ํ˜•์„ฑ๊ณผ์ • ์• ๋‹ˆ๋ฉ”์ด์…˜"""
 
 
 
 
 
 
 
 
 
 
 
451
  h, w = grid_size, grid_size
452
  elevation = np.zeros((h, w))
 
453
 
454
  apex_y = int(h * 0.15)
455
  center_x = w // 2
@@ -469,19 +557,62 @@ def create_alluvial_fan_animated(grid_size: int, stage: float,
469
  max_reach = int((h - apex_y) * stage)
470
  half_angle = np.radians(cone_angle / 2) * (0.5 + 0.5 * stage)
471
 
472
- for r in range(apex_y, min(apex_y + max_reach, h)): # h ๋ฒ”์œ„ ์ฒดํฌ ์ถ”๊ฐ€
 
 
 
 
 
473
  dist = r - apex_y
 
 
 
 
 
 
 
 
 
474
  for c in range(w):
475
  dx = c - center_x
476
  if abs(np.arctan2(dx, max(dist, 1))) < half_angle:
477
  radial = np.sqrt(dx**2 + dist**2)
478
- z = max_height * (1 - radial / (max_reach * 1.5 + 0.001)) * stage # divide by zero ๋ฐฉ์ง€
479
  lateral_decay = 1 - abs(dx) / (w // 2)
480
- elevation[r, c] = max(0, z * lateral_decay)
481
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
482
  return elevation
483
 
484
 
 
 
 
 
 
 
 
 
 
 
 
 
485
  def create_meander_animated(grid_size: int, stage: float,
486
  amplitude: float = 0.3, num_bends: int = 3) -> np.ndarray:
487
  """๊ณก๋ฅ˜ ํ˜•์„ฑ๊ณผ์ • ์• ๋‹ˆ๋ฉ”์ด์…˜ (์ง์„  โ†’ ์‚ฌํ–‰ โ†’ ์šฐ๊ฐํ˜ธ โ†’ ํ•˜์ค‘๋„)
@@ -841,22 +972,41 @@ def create_barchan_animated(grid_size: int, stage: float,
841
  # ============================================
842
 
843
  def create_incised_meander(grid_size: int = 100, stage: float = 1.0,
844
- valley_depth: float = 80.0, num_terraces: int = 3) -> np.ndarray:
 
845
  """
846
  ๊ฐ์ž…๊ณก๋ฅ˜ (Incised Meander) + ํ•˜์•ˆ๋‹จ๊ตฌ (River Terraces)
847
 
 
 
 
 
848
  ์œต๊ธฐ ํ™˜๊ฒฝ์—์„œ ๊ณก๋ฅ˜๊ฐ€ ์•”๋ฐ˜์„ ํŒŒ๊ณ  ๋“ค์–ด๊ฐ€๋ฉด์„œ ํ˜•์„ฑ
849
  """
850
  h, w = grid_size, grid_size
851
  elevation = np.zeros((h, w))
852
 
853
  center_x = w // 2
854
- amplitude = w * 0.25 * stage
855
- wl = h / 3 # 3 bends
856
  channel_width = max(3, w // 25)
857
 
858
- # ๊ธฐ๋ฐ˜ ๊ณ ์›
859
- elevation[:, :] = valley_depth
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
860
 
861
  # ๊ฐ์ž… ๊ณก๋ฅ˜ ํŒŒ๊ธฐ
862
  for r in range(h):
@@ -866,121 +1016,295 @@ def create_incised_meander(grid_size: int = 100, stage: float = 1.0,
866
  for c in range(w):
867
  dist = abs(c - meander_x)
868
 
 
 
 
869
  if dist < channel_width:
870
  # ํ•˜๋„ (๊ฐ€์žฅ ๊นŠ์Œ)
871
- elevation[r, c] = 5.0
872
- elif dist < channel_width * 2:
873
- # ๊ธ‰๊ฒฝ์‚ฌ ์ธก๋ฒฝ
874
- t = (dist - channel_width) / channel_width
875
- elevation[r, c] = 5.0 + (valley_depth - 5.0) * t
876
-
877
- # ํ•˜์•ˆ๋‹จ๊ตฌ (๊ณ„๋‹จ)
878
- terrace_heights = [valley_depth * (0.3 + 0.2 * i) for i in range(num_terraces)]
879
 
880
- for terrace_h in terrace_heights:
881
- for r in range(h):
882
- theta = 2 * np.pi * r / wl
883
- meander_x = center_x + amplitude * np.sin(theta) * 0.8
 
 
 
 
 
884
 
885
- for c in range(w):
886
- dist = abs(c - meander_x)
887
- if channel_width * 3 < dist < channel_width * 4:
888
- if elevation[r, c] > terrace_h:
889
- elevation[r, c] = terrace_h
890
-
 
 
 
 
 
 
 
 
 
 
 
891
  return elevation
892
 
893
 
 
 
 
 
 
 
 
 
 
 
 
 
894
  def create_free_meander(grid_size: int = 100, stage: float = 1.0,
895
- num_bends: int = 4) -> np.ndarray:
896
  """
897
  ์ž์œ ๊ณก๋ฅ˜ (Free Meander) + ๋ฒ”๋žŒ์› (Floodplain) + ์ž์—ฐ์ œ๋ฐฉ (Natural Levee)
898
 
899
- ์ถฉ์  ํ‰์•ผ ์œ„๋ฅผ ์ž์œ ๋กญ๊ฒŒ ์‚ฌํ–‰
 
 
 
 
 
 
 
 
 
900
  """
901
  h, w = grid_size, grid_size
902
  elevation = np.zeros((h, w))
903
 
904
  # ๋ฒ”๋žŒ์› ๊ธฐ๋ฐ˜
905
- elevation[:, :] = 10.0
 
906
 
907
  center_x = w // 2
908
- amplitude = w * 0.3 * stage
909
- wl = h / num_bends
910
  channel_width = max(3, w // 20)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
911
 
912
  for r in range(h):
913
  theta = 2 * np.pi * r / wl
914
  meander_x = center_x + amplitude * np.sin(theta)
915
 
 
 
 
916
  for c in range(w):
917
- dist = abs(c - meander_x)
 
918
 
919
- if dist < channel_width:
920
- # ํ•˜๋„
921
- elevation[r, c] = 5.0 - (channel_width - dist) * 0.2
922
- elif dist < channel_width * 2:
923
- # ์ž์—ฐ์ œ๋ฐฉ (Levee) - ํ•˜๋„๋ณด๋‹ค ์•ฝ๊ฐ„ ๋†’์Œ
924
- elevation[r, c] = 11.0
925
- elif dist < channel_width * 4:
926
- # ๋ฐฐํ›„์Šต์ง€ (Backswamp) - ์•ฝ๊ฐ„ ๋‚ฎ์Œ
927
- elevation[r, c] = 9.5
 
 
 
 
 
 
 
 
 
 
 
928
 
929
- # ์šฐ๊ฐํ˜ธ (Oxbow Lake)
 
 
 
 
 
930
  if stage > 0.7:
 
931
  oxbow_y = h // 2
 
 
932
  for dy in range(-int(wl/4), int(wl/4)):
933
  r = oxbow_y + dy
934
  if 0 <= r < h:
935
  theta = 2 * np.pi * dy / (wl/2)
936
- ox_x = center_x + amplitude * 1.3 * np.sin(theta)
937
- for dc in range(-channel_width, channel_width + 1):
 
938
  c = int(ox_x + dc)
939
  if 0 <= c < w:
940
- elevation[r, c] = 4.5
941
-
 
 
 
 
 
 
 
 
 
 
 
 
942
  return elevation
943
 
944
 
945
- def create_bird_foot_delta(grid_size: int = 100, stage: float = 1.0) -> np.ndarray:
946
- """์กฐ์กฑ์ƒ ์‚ผ๊ฐ์ฃผ (Bird-foot Delta) - ๋ฏธ์‹œ์‹œํ”ผ๊ฐ•ํ˜•"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
947
  h, w = grid_size, grid_size
948
  elevation = np.zeros((h, w))
949
  elevation[:, :] = -5.0 # ๋ฐ”๋‹ค
950
 
951
- apex_y = int(h * 0.15)
952
  center_x = w // 2
953
 
954
- # ๊ฐ€๋Š˜๊ณ  ๊ธด ๋ถ„๋ฐฐ์ˆ˜๋กœ๋“ค
955
- num_fingers = 5
956
- max_length = int((h - apex_y) * stage)
 
 
 
 
 
 
 
 
 
957
 
958
  for i in range(num_fingers):
959
- angle = np.radians(-30 + 15 * i) # -30 to +30 degrees
 
 
 
 
 
 
960
 
961
  for d in range(max_length):
962
  r = apex_y + int(d * np.cos(angle))
963
  c = center_x + int(d * np.sin(angle))
964
 
965
  if 0 <= r < h and 0 <= c < w:
966
- # ์ข์€ finger ํ˜•ํƒœ
967
- for dc in range(-3, 4):
 
 
968
  for dr in range(-2, 3):
969
  nr, nc = r + dr, c + dc
970
  if 0 <= nr < h and 0 <= nc < w:
971
  dist = np.sqrt(dr**2 + dc**2)
972
- z = 8.0 * (1 - d / max_length) * (1 - dist / 4) * stage
973
- elevation[nr, nc] = max(elevation[nr, nc], z)
974
 
975
- # ํ•˜์ฒœ
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
976
  for r in range(apex_y):
977
- for dc in range(-3, 4):
978
  if 0 <= center_x + dc < w:
979
- elevation[r, center_x + dc] = 6.0
980
-
 
 
 
 
 
 
 
 
 
981
  return elevation
982
 
983
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
984
  def create_arcuate_delta(grid_size: int = 100, stage: float = 1.0) -> np.ndarray:
985
  """ํ˜ธ์ƒ ์‚ผ๊ฐ์ฃผ (Arcuate Delta) - ๋‚˜์ผ๊ฐ•ํ˜•"""
986
  h, w = grid_size, grid_size
@@ -1050,18 +1374,49 @@ def create_cuspate_delta(grid_size: int = 100, stage: float = 1.0) -> np.ndarray
1050
 
1051
 
1052
  def create_cirque(grid_size: int = 100, stage: float = 1.0,
1053
- depth: float = 50.0) -> np.ndarray:
1054
- """๊ถŒ๊ณก (Cirque) - ๋น™ํ•˜ ์‹œ์ž‘์ """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1055
  h, w = grid_size, grid_size
1056
  elevation = np.zeros((h, w))
1057
 
1058
  # ์‚ฐ์•… ๋ฐฐ๊ฒฝ
1059
- elevation[:, :] = depth + 30.0
 
1060
 
1061
  # ๊ถŒ๊ณก ์œ„์น˜ (์ƒ๋‹จ ์ค‘์•™)
1062
- cirque_y = int(h * 0.3)
1063
  cirque_x = w // 2
1064
- cirque_radius = int(w * 0.25 * (0.5 + 0.5 * stage))
 
 
 
 
 
 
 
 
 
 
 
 
1065
 
1066
  for r in range(h):
1067
  for c in range(w):
@@ -1070,53 +1425,185 @@ def create_cirque(grid_size: int = 100, stage: float = 1.0,
1070
  dist = np.sqrt(dy**2 + dx**2)
1071
 
1072
  if dist < cirque_radius:
1073
- # ๋ฐ˜์›ํ˜• ์›€ํ‘นํ•œ ํ˜•ํƒœ
1074
- # ๋ฐ”๋‹ฅ์€ ํ‰ํƒ„, ํ›„๋ฒฝ(headwall)์€ ๊ธ‰๊ฒฝ์‚ฌ
1075
- if dy < 0: # ํ›„๋ฒฝ
1076
- z = depth * (1 - dist / cirque_radius) * 0.3
1077
- else: # ๋ฐ”๋‹ฅ
1078
- z = depth * 0.1
1079
- elevation[r, c] = z
1080
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1081
  return elevation
1082
 
1083
 
1084
- def create_horn(grid_size: int = 100, stage: float = 1.0) -> np.ndarray:
1085
- """ํ˜ธ๋ฅธ (Horn) - ํ”ผ๋ผ๋ฏธ๋“œํ˜• ๋ด‰์šฐ๋ฆฌ"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1086
  h, w = grid_size, grid_size
1087
  elevation = np.zeros((h, w))
1088
 
1089
  center = (h // 2, w // 2)
1090
- peak_height = 100.0 * stage
 
 
 
 
 
 
 
 
1091
 
1092
- # 4๋ฐฉํ–ฅ ๊ถŒ๊ณก์— ์˜ํ•œ ํ˜ธ๋ฅธ ํ˜•์„ฑ
1093
- num_cirques = 4
1094
- cirque_radius = int(w * 0.3)
1095
 
 
1096
  for r in range(h):
1097
  for c in range(w):
1098
  dy = r - center[0]
1099
  dx = c - center[1]
1100
  dist = np.sqrt(dy**2 + dx**2)
1101
 
1102
- # ๊ธฐ๋ณธ ํ”ผ๋ผ๋ฏธ๋“œ ํ˜•ํƒœ
1103
- elevation[r, c] = peak_height * max(0, 1 - dist / (w // 2))
1104
-
1105
- # 4๋ฐฉํ–ฅ ๊ถŒ๊ณก ํŒŒ๊ธฐ
1106
- for i in range(num_cirques):
1107
- angle = i * np.pi / 2
1108
- cx = center[1] + int(cirque_radius * 0.8 * np.cos(angle))
1109
- cy = center[0] + int(cirque_radius * 0.8 * np.sin(angle))
1110
-
 
 
 
 
 
 
1111
  cdist = np.sqrt((r - cy)**2 + (c - cx)**2)
 
1112
  if cdist < cirque_radius * 0.6:
1113
- # ๊ถŒ๊ณก ํŒŒ๊ธฐ
1114
- elevation[r, c] = min(elevation[r, c],
1115
- 20.0 + 30.0 * (cdist / (cirque_radius * 0.6)))
1116
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1117
  return elevation
1118
 
1119
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1120
  def create_shield_volcano(grid_size: int = 100, stage: float = 1.0,
1121
  max_height: float = 40.0) -> np.ndarray:
1122
  """์ˆœ์ƒํ™”์‚ฐ (Shield Volcano) - ์™„๋งŒํ•œ ๊ฒฝ์‚ฌ"""
@@ -1272,15 +1759,29 @@ def create_spit_lagoon(grid_size: int = 100, stage: float = 1.0) -> np.ndarray:
1272
  # ์ถ”๊ฐ€ ์ง€ํ˜• (Additional Landforms)
1273
  # ============================================
1274
 
1275
- def create_fjord(grid_size: int = 100, stage: float = 1.0) -> np.ndarray:
 
1276
  """ํ”ผ์˜ค๋ฅด๋“œ (Fjord) - ๋น™ํ•˜ ํ›„ํ‡ด ํ›„ ๋ฐ”๋‹ค ์œ ์ž…
1277
 
1278
  Stage 0.0~0.4: ๋น™ํ•˜๊ฐ€ U์ž๊ณก์„ ์ฑ„์›€ (๋น™ํ•˜๊ธฐ)
1279
- Stage 0.4~0.7: ๋น™ํ•˜ ํ›„ํ‡ด ์‹œ์ž‘ (๋ฐ”๋‹ค ์œ ์ž… ์‹œ์ž‘)
1280
- Stage 0.7~1.0: ๋น™ํ•˜ ์™„์ „ ํ›„ํ‡ด (ํ”ผ์˜ค๋ฅด๋“œ ์™„์„ฑ)
 
 
 
 
 
 
 
 
 
 
 
1281
  """
1282
  h, w = grid_size, grid_size
1283
  elevation = np.zeros((h, w))
 
 
1284
 
1285
  # ์‚ฐ์•… ์ง€ํ˜• (๋†’์€ ์‚ฐ)
1286
  elevation[:, :] = 100.0
@@ -1289,72 +1790,103 @@ def create_fjord(grid_size: int = 100, stage: float = 1.0) -> np.ndarray:
1289
  valley_width = int(w * 0.25)
1290
  valley_depth = 60.0
1291
 
1292
- # U์ž๊ณก ํ˜•์„ฑ
1293
  for r in range(h):
1294
  for c in range(w):
1295
  dx = abs(c - center)
1296
 
1297
  if dx < valley_width:
1298
- # U์ž ๋ฐ”๋‹ฅ
1299
- base_height = 10.0
1300
- elevation[r, c] = base_height
 
1301
  elif dx < valley_width + 15:
1302
- # U์ž ์ธก๋ฒฝ (์ˆ˜์ง์— ๊ฐ€๊นŒ์›€)
1303
  t = (dx - valley_width) / 15
1304
- elevation[r, c] = 10.0 + 90.0 * (t ** 0.5) # ๊ธ‰๊ฒฝ์‚ฌ
 
1305
 
1306
  # ๋น™ํ•˜ / ๋ฐ”๋‹ค ์ƒํƒœ
1307
  if stage < 0.4:
1308
  # ๋น™ํ•˜๊ธฐ: U์ž๊ณก์— ๋น™ํ•˜ ์ฑ„์›€
1309
- glacier_extent = int(h * 0.9) # ๊ฑฐ์˜ ์ „์ฒด ์ฑ„์›€
1310
- glacier_thickness = 40.0
 
 
1311
 
1312
  for r in range(glacier_extent):
1313
  for c in range(w):
1314
  dx = abs(c - center)
1315
  if dx < valley_width:
1316
- # ๋น™ํ•˜ ํ‘œ๋ฉด (๋ณผ๋ก)
1317
  cross_profile = glacier_thickness * (1 - (dx / valley_width) ** 2)
1318
- elevation[r, c] = 10.0 + cross_profile
1319
 
1320
  elif stage < 0.7:
1321
- # ๋น™ํ•˜ ํ›„ํ‡ด ์ค‘: ์ผ๋ถ€ ๋น™ํ•˜ + ๋ฐ”๋‹ค ์œ ์ž…
1322
  retreat_factor = (stage - 0.4) / 0.3
1323
 
1324
  # ๋น™ํ•˜ ์ž”๋ฅ˜ (์ƒ๋ฅ˜์—๋งŒ)
1325
- glacier_end = int(h * (0.9 - 0.6 * retreat_factor))
1326
- glacier_thickness = 40.0 * (1 - retreat_factor * 0.5)
 
 
 
1327
 
1328
  for r in range(glacier_end):
1329
  for c in range(w):
1330
  dx = abs(c - center)
1331
  if dx < valley_width:
1332
  cross_profile = glacier_thickness * (1 - (dx / valley_width) ** 2)
1333
- elevation[r, c] = 10.0 + cross_profile
1334
 
1335
  # ๋ฐ”๋‹ค ์œ ์ž… (ํ•˜๋ฅ˜๋ถ€ํ„ฐ)
1336
- sea_start = glacier_end
1337
- for r in range(sea_start, h):
1338
  for c in range(w):
1339
  dx = abs(c - center)
1340
  if dx < valley_width:
1341
- # ๊นŠ์€ ๋ฐ”๋‹ค
1342
- elevation[r, c] = -30.0 * retreat_factor
1343
  else:
1344
- # ํ”ผ์˜ค๋ฅด๋“œ ์™„์„ฑ: ๊นŠ์€ ๋ฐ”๋‹ค๋งŒ
1345
- sea_depth = -50.0 # ๊นŠ์€ ํ”ผ์˜ค๋ฅด๋“œ
1346
 
1347
  for r in range(h):
1348
  for c in range(w):
1349
  dx = abs(c - center)
1350
  if dx < valley_width:
1351
- # ์ƒ๋ฅ˜๋กœ ๊ฐˆ์ˆ˜๋ก ์–•์•„์ง
1352
- depth_gradient = 1 - (r / h) * 0.3
1353
- elevation[r, c] = sea_depth * depth_gradient
1354
-
 
 
 
 
 
 
 
 
 
 
 
1355
  return elevation
1356
 
1357
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1358
  def create_drumlin(grid_size: int = 100, stage: float = 1.0,
1359
  num_drumlins: int = 5) -> np.ndarray:
1360
  """๋“œ๋Ÿผ๋ฆฐ (Drumlin) - ๋น™ํ•˜ ๋ฐฉํ–ฅ ํƒ€์›ํ˜• ์–ธ๋•"""
@@ -1475,83 +2007,121 @@ def create_braided_river(grid_size: int = 100, stage: float = 1.0) -> np.ndarray
1475
 
1476
 
1477
  def create_waterfall(grid_size: int = 100, stage: float = 1.0,
1478
- drop_height: float = 50.0) -> np.ndarray:
1479
  """ํญํฌ (Waterfall) - ๋‘๋ถ€์นจ์‹์œผ๋กœ ํ›„ํ‡ด
1480
 
1481
- Stage 0.0: ํญํฌ๊ฐ€ ํ•˜๋ฅ˜์— ์œ„์น˜
1482
- Stage 1.0: ํญํฌ๊ฐ€ ์ƒ๋ฅ˜๋กœ ํ›„ํ‡ด (๋‘๋ถ€์นจ์‹)
1483
- - ๊ฒฝ์•”์ธต๊ณผ ์—ฐ์•”์ธต์˜ ์ฐจ๋ณ„์นจ์‹
1484
- - ํ”Œ๋Ÿฐ์ง€ํ’€(ํญํ˜ธ) ๋ฐœ๋‹ฌ
1485
- - ํ›„ํ‡ดํ•˜๋ฉด์„œ ํ˜‘๊ณก ํ˜•์„ฑ
 
 
 
 
 
 
1486
  """
1487
  h, w = grid_size, grid_size
1488
  elevation = np.zeros((h, w))
1489
  center = w // 2
1490
 
1491
  # ํญํฌ ์œ„์น˜ (stage์— ๋”ฐ๋ผ ์ƒ๋ฅ˜๋กœ ํ›„ํ‡ด)
1492
- # stage 0: ํ•˜๋ฅ˜(h*0.7), stage 1: ์ƒ๋ฅ˜(h*0.3)
1493
- initial_fall = int(h * 0.7)
1494
- final_fall = int(h * 0.3)
1495
- fall_r = int(initial_fall - (initial_fall - final_fall) * stage)
1496
 
1497
- # ์ƒ๋ฅ˜ (๋†’์€ ๊ฒฝ์•”์ธต)
1498
  hard_rock_height = drop_height + 30.0
 
1499
  for r in range(fall_r):
1500
  for c in range(w):
1501
  # ์ƒ๋ฅ˜๋กœ ๊ฐˆ์ˆ˜๋ก ๋†’์•„์ง
1502
  upstream_rise = (fall_r - r) * 0.3
1503
  elevation[r, c] = hard_rock_height + upstream_rise
1504
 
1505
- # ํญํฌ ์ ˆ๋ฒฝ (๊ธ‰๊ฒฝ์‚ฌ)
1506
- cliff_width = 5
1507
  for r in range(fall_r, min(fall_r + cliff_width, h)):
1508
  for c in range(w):
1509
  t = (r - fall_r) / cliff_width
1510
- # ์ˆ˜์ง ๋‚™ํ•˜
1511
- elevation[r, c] = hard_rock_height * (1 - t) + 10.0 * t
1512
 
1513
- # ํ•˜๋ฅ˜ (์—ฐ์•”์ธต ์นจ์‹๋จ)
1514
  for r in range(fall_r + cliff_width, h):
1515
  for c in range(w):
1516
- # ํ•˜๋ฅ˜๋กœ ๊ฐˆ์ˆ˜๋ก ๋‚ฎ์•„์ง
1517
- downstream_drop = (r - fall_r - cliff_width) * 0.2
1518
  elevation[r, c] = 10.0 - downstream_drop
1519
 
1520
- # ํ˜‘๊ณก (ํญํฌ ํ›„ํ‡ด ๊ฒฝ๋กœ)
1521
  gorge_start = fall_r + cliff_width
1522
- gorge_end = initial_fall + 10 # ์›๋ž˜ ํญํฌ ์œ„์น˜๊นŒ์ง€
1523
- gorge_depth = 8.0
 
1524
 
1525
  for r in range(gorge_start, min(gorge_end, h)):
1526
- for dc in range(-6, 7):
1527
  c = center + dc
1528
  if 0 <= c < w:
1529
  # V์ž ํ˜‘๊ณก ๋‹จ๋ฉด
1530
- depth = gorge_depth * (1 - abs(dc) / 6)
1531
  elevation[r, c] -= depth
1532
 
1533
  # ํ•˜์ฒœ ์ˆ˜๋กœ
 
1534
  for r in range(h):
1535
- for dc in range(-4, 5):
1536
  c = center + dc
1537
  if 0 <= c < w:
1538
- elevation[r, c] -= 3.0
1539
-
1540
- # ํ”Œ๋Ÿฐ์ง€ํ’€ (ํญํ˜ธ) - ํญํฌ ๋ฐ”๋กœ ์•„๋ž˜
1541
- pool_r = fall_r + cliff_width + 2
1542
- pool_depth = 15.0
1543
- for dr in range(-6, 7):
1544
- for dc in range(-7, 8):
1545
- r, c = pool_r + dr, center + dc
1546
- if 0 <= r < h and 0 <= c < w:
 
 
 
1547
  dist = np.sqrt(dr**2 + dc**2)
1548
- if dist < 7:
1549
- pool_effect = pool_depth * (1 - dist / 7)
1550
- elevation[r, c] = min(elevation[r, c], 5.0 - pool_effect)
 
 
 
 
 
 
 
 
 
 
 
 
 
1551
 
1552
  return elevation
1553
 
1554
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1555
  def create_karst_doline(grid_size: int = 100, stage: float = 1.0,
1556
  num_dolines: int = 5) -> np.ndarray:
1557
  """๋Œ๋ฆฌ๋„ค (Doline/Sinkhole) - ์นด๋ฅด์ŠคํŠธ ์ง€ํ˜•"""
 
332
  return elevation
333
 
334
 
335
+ def create_coastal_cliff(grid_size: int = 100, stage: float = 1.0,
336
  cliff_height: float = 30.0,
337
+ num_stacks: int = 2,
338
+ return_metadata: bool = False) -> np.ndarray:
339
  """
340
+ ํ•ด์•ˆ ์ ˆ๋ฒฝ (Coastal Cliff) + ํŒŒ์‹๋Œ€ + ์‹œ์Šคํƒ
341
 
342
+ Stage 0~0.3: ์ดˆ๊ธฐ ํ•ด์•ˆ (์ ˆ๋ฒฝ ํ˜•์„ฑ ์‹œ์ž‘)
343
+ - ํŒŒ๋ž‘์˜ ์ˆ˜์••์ž‘์šฉ(hydraulic action)
344
+ - ๋…ธ์น˜(notch) ํ˜•์„ฑ ์‹œ์ž‘
345
+
346
+ Stage 0.3~0.6: ์ ˆ๋ฒฝ ๋ฐœ๋‹ฌ
347
+ - ์—ฐ๋งˆ์ž‘์šฉ(abrasion)์œผ๋กœ ๋…ธ์น˜ ํ™•๋Œ€
348
+ - ์˜ค๋ฒ„ํ–‰(overhang) ํ˜•์„ฑ
349
+ - ์ ˆ๋ฒฝ ๋ถ•๊ดด ์‹œ์ž‘
350
+
351
+ Stage 0.6~0.8: ์ ˆ๋ฒฝ ํ›„ํ‡ด
352
+ - ๋ฐ˜๋ณต์  ๋ถ•๊ดด๋กœ ์ ˆ๋ฒฝ์ด ์œก์ง€์ชฝ์œผ๋กœ ํ›„ํ‡ด
353
+ - ํŒŒ์‹๋Œ€(wave-cut platform) ํ™•์žฅ
354
+
355
+ Stage 0.8~1.0: ์‹œ์Šคํƒ/ํ•ด์‹๋™ ํ˜•์„ฑ
356
+ - ์—ฐ์•ฝ๋ถ€ ์ฐจ๋ณ„์นจ์‹
357
+ - ํ•ด์‹์•„์น˜ โ†’ ์‹œ์Šคํƒ ํ˜•์„ฑ
358
+
359
+ ํ•ต์‹ฌ ๊ณผ์ •:
360
+ - ์ˆ˜์••์ž‘์šฉ: ํŒŒ๋ž‘ ์ถฉ๊ฒฉ โ†’ ์•”์„ ํ‹ˆ์ƒˆ ์••์ถ•๊ณต๊ธฐ
361
+ - ์—ฐ๋งˆ์ž‘์šฉ: ํ•ด๋นˆ ์ž๊ฐˆ/๋ชจ๋ž˜๊ฐ€ ์ ˆ๋ฒฝ ๊นŽ์Œ
362
+ - ์šฉ์‹์ž‘์šฉ: ํ•ด์ˆ˜์˜ ํ™”ํ•™์  ์šฉํ•ด (์„ํšŒ์•”)
363
  """
364
  h, w = grid_size, grid_size
365
  elevation = np.zeros((h, w))
366
 
367
+ # ํ•ด์•ˆ์„  ์œ„์น˜ (stage์— ๋”ฐ๋ผ ์œก์ง€์ชฝ์œผ๋กœ ํ›„ํ‡ด)
368
+ initial_sea_line = int(h * 0.7)
369
+ retreat_amount = int(h * 0.2 * stage)
370
+ sea_line = initial_sea_line - retreat_amount
371
+
372
  # ๋ฐ”๋‹ค (ํ•˜๋‹จ)
373
+ for r in range(sea_line, h):
374
+ elevation[r, :] = -5.0
375
+
376
+ # ์ ˆ๋ฒฝ ๋†’์ด (stage์— ๋”ฐ๋ผ ๋ฐœ๋‹ฌ)
377
+ current_cliff_height = cliff_height * (0.5 + 0.5 * stage)
378
 
379
  # ์œก์ง€ + ์ ˆ๋ฒฝ
380
+ cliff_width = max(3, int(5 * stage))
381
  for r in range(sea_line):
382
  cliff_dist = sea_line - r
383
+ if cliff_dist < cliff_width:
384
+ # ์ ˆ๋ฒฝ๋ฉด (์ˆ˜์ง์— ๊ฐ€๊นŒ์›€)
385
+ t = cliff_dist / cliff_width
386
+ elevation[r, :] = current_cliff_height * (t ** 0.7) # ์˜ค๋ชฉํ•œ ํ”„๋กœํŒŒ์ผ
387
  else:
388
  # ํ‰ํƒ„ํ•œ ์œก์ง€
389
+ elevation[r, :] = current_cliff_height
390
+
391
+ # ๋…ธ์น˜ (Notch) - stage > 0.3์—์„œ ํ˜•์„ฑ
392
+ if stage > 0.3:
393
+ notch_depth = int(3 * (stage - 0.3) / 0.7)
394
+ notch_height = 2 # ํŒŒ๋ž‘๋Œ€ ๋†’์ด
 
 
 
 
 
395
 
396
+ for r in range(sea_line - notch_height, sea_line):
397
+ for c in range(w):
398
+ if 0 <= r < h:
399
+ # ๋…ธ์น˜ ๊นŠ์ด๋งŒํผ ํŒŒ์ž„
400
+ elevation[r, c] = min(elevation[r, c],
401
+ elevation[r, c] - notch_depth * (1 - abs(r - (sea_line - 1)) / notch_height))
402
+
403
+ # ํŒŒ์‹๋Œ€ (Wave-cut Platform) - stage > 0.4์—์„œ ํ™•์žฅ
404
+ platform_width = int(10 + 15 * max(0, (stage - 0.4) / 0.6))
405
+ for r in range(sea_line, min(sea_line + platform_width, h)):
406
+ platform_depth = -1.0 - (r - sea_line) * 0.3
407
+ elevation[r, :] = max(platform_depth, -5.0)
408
+
409
+ # ์‹œ์Šคํƒ (Sea Stacks) - stage > 0.7์—์„œ ํ˜•์„ฑ
410
+ stacks_formed = []
411
+ if stage > 0.7:
412
+ stack_progress = (stage - 0.7) / 0.3
413
+ visible_stacks = int(num_stacks * stack_progress) + 1
414
 
415
+ for i in range(min(visible_stacks, num_stacks)):
416
+ sx = w // 4 + i * (w // 2)
417
+ sy = sea_line + 8 + i * 4
418
+
419
+ stack_height = current_cliff_height * 0.6 * stack_progress
420
+ stack_radius = 4
421
+
422
+ for dr in range(-stack_radius, stack_radius + 1):
423
+ for dc in range(-stack_radius, stack_radius + 1):
424
+ r_pos, c_pos = sy + dr, sx + dc
425
+ if 0 <= r_pos < h and 0 <= c_pos < w:
426
+ dist = np.sqrt(dr**2 + dc**2)
427
+ if dist < stack_radius:
428
+ z = stack_height * (1 - (dist / stack_radius) ** 2)
429
+ elevation[r_pos, c_pos] = max(elevation[r_pos, c_pos], z)
430
+
431
+ stacks_formed.append((sy, sx))
432
+
433
+ if return_metadata:
434
+ return elevation, {
435
+ 'sea_line': sea_line,
436
+ 'cliff_height': current_cliff_height,
437
+ 'retreat_amount': retreat_amount,
438
+ 'platform_width': platform_width,
439
+ 'stacks_formed': stacks_formed,
440
+ 'erosion_processes': {
441
+ 'hydraulic_action': 'ํŒŒ๋ž‘ ์ถฉ๊ฒฉ โ†’ ์•”์„ ํ‹ˆ์ƒˆ ์••์ถ•๊ณต๊ธฐ',
442
+ 'abrasion': 'ํ•ด๋นˆ ์ž๊ฐˆ/๋ชจ๋ž˜๊ฐ€ ์ ˆ๋ฒฝ ์—ฐ๋งˆ',
443
+ 'corrosion': 'ํ•ด์ˆ˜์˜ ํ™”ํ•™์  ์šฉํ•ด (์„ํšŒ์•”)'
444
+ },
445
+ 'stage_description': _get_cliff_stage_desc(stage)
446
+ }
447
+
448
  return elevation
449
 
450
 
451
+ def _get_cliff_stage_desc(stage: float) -> str:
452
+ """ํ•ด์•ˆ์ ˆ๋ฒฝ ๋‹จ๊ณ„๋ณ„ ์„ค๋ช…"""
453
+ if stage < 0.2:
454
+ return "๐ŸŒŠ ์ดˆ๊ธฐ ํ•ด์•ˆ: ํŒŒ๋ž‘ ์นจ์‹ ์‹œ์ž‘"
455
+ elif stage < 0.4:
456
+ return "โ›๏ธ ๋…ธ์น˜ ํ˜•์„ฑ: ์ˆ˜์••์ž‘์šฉ์œผ๋กœ ํŒŒ๋ž‘๋Œ€ ์นจ์‹"
457
+ elif stage < 0.6:
458
+ return "๐Ÿ”๏ธ ์ ˆ๋ฒฝ ๋ฐœ๋‹ฌ: ์˜ค๋ฒ„ํ–‰ ํ˜•์„ฑ โ†’ ๋ถ•๊ดด"
459
+ elif stage < 0.8:
460
+ return "๐Ÿ“‰ ์ ˆ๋ฒฝ ํ›„ํ‡ด: ํŒŒ์‹๋Œ€ ๋…ธ์ถœ"
461
+ else:
462
+ return "๐Ÿชจ ์‹œ์Šคํƒ ํ˜•์„ฑ: ์ฐจ๋ณ„์นจ์‹์œผ๋กœ ๊ณ ๋ฆฝ ์•”์„"
463
+
464
+
465
  # ============================================
466
  # ์• ๋‹ˆ๋ฉ”์ด์…˜์šฉ ํ˜•์„ฑ๊ณผ์ • ํ•จ์ˆ˜ (Stage-based)
467
  # stage: 0.0 (์‹œ์ž‘) ~ 1.0 (์™„์„ฑ)
 
522
 
523
 
524
  def create_alluvial_fan_animated(grid_size: int, stage: float,
525
+ cone_angle: float = 90.0, max_height: float = 50.0,
526
+ return_metadata: bool = False) -> np.ndarray:
527
+ """์„ ์ƒ์ง€ ํ˜•์„ฑ๊ณผ์ • ์• ๋‹ˆ๋ฉ”์ด์…˜
528
+
529
+ Stage 0~0.3: ์„ ์ •(Apex) ํ˜•์„ฑ - ํ˜‘๊ณก ์ถœ๊ตฌ, ์—ญ ํ‡ด์ 
530
+ Stage 0.3~0.7: ์„ ์•™(Mid-fan) ํ™•์žฅ - ๋ถ„๊ธฐ ์ˆ˜๋กœ, ์‚ฌ์งˆ ํ‡ด์ 
531
+ Stage 0.7~1.0: ์„ ๋‹จ(Toe) ์™„์„ฑ - ๋ง๋‹จ๋ถ€, ๋‹ˆ์งˆ ํ‡ด์ 
532
+
533
+ ์„ธ๋ถ€ ๊ตฌ์กฐ:
534
+ - ์„ ์ •: ๊ฒฝ์‚ฌ 5-15ยฐ, ์—ญ(Gravel) ํ‡ด์ , ๋‹จ์ผ ์ฃผ์ˆ˜๋กœ
535
+ - ์„ ์•™: ๊ฒฝ์‚ฌ 2-5ยฐ, ์‚ฌ(Sand) ํ‡ด์ , ๋ถ„๊ธฐ ์ˆ˜๋กœ
536
+ - ์„ ๋‹จ: ๊ฒฝ์‚ฌ <2ยฐ, ๋‹ˆ(Silt) ํ‡ด์ , ๋ง์ƒ/์‹œ์ƒ ์ˆ˜๋กœ
537
+ """
538
  h, w = grid_size, grid_size
539
  elevation = np.zeros((h, w))
540
+ zone_mask = np.zeros((h, w), dtype=int) # 0: ์—†์Œ, 1: ์„ ์ •, 2: ์„ ์•™, 3: ์„ ๋‹จ
541
 
542
  apex_y = int(h * 0.15)
543
  center_x = w // 2
 
557
  max_reach = int((h - apex_y) * stage)
558
  half_angle = np.radians(cone_angle / 2) * (0.5 + 0.5 * stage)
559
 
560
+ # ์กด ๊ฒฝ๊ณ„ ๊ณ„์‚ฐ
561
+ apex_end = apex_y + max(1, int(max_reach * 0.2)) # ์„ ์ •: 0~20%
562
+ mid_end = apex_y + max(1, int(max_reach * 0.6)) # ์„ ์•™: 20~60%
563
+ # ์„ ๋‹จ: 60~100%
564
+
565
+ for r in range(apex_y, min(apex_y + max_reach, h)):
566
  dist = r - apex_y
567
+
568
+ # ์กด ๊ฒฐ์ •
569
+ if r < apex_end:
570
+ current_zone = 1 # ์„ ์ •
571
+ elif r < mid_end:
572
+ current_zone = 2 # ์„ ์•™
573
+ else:
574
+ current_zone = 3 # ์„ ๋‹จ
575
+
576
  for c in range(w):
577
  dx = c - center_x
578
  if abs(np.arctan2(dx, max(dist, 1))) < half_angle:
579
  radial = np.sqrt(dx**2 + dist**2)
580
+ z = max_height * (1 - radial / (max_reach * 1.5 + 0.001)) * stage
581
  lateral_decay = 1 - abs(dx) / (w // 2)
582
+ new_elevation = max(0, z * lateral_decay)
583
 
584
+ if new_elevation > 0:
585
+ elevation[r, c] = new_elevation
586
+ zone_mask[r, c] = current_zone
587
+
588
+ if return_metadata:
589
+ return elevation, {
590
+ 'zone_mask': zone_mask,
591
+ 'apex_boundary': apex_end,
592
+ 'mid_boundary': mid_end,
593
+ 'stage_description': _get_fan_stage_desc(stage),
594
+ 'zone_info': {
595
+ 1: {'name': '์„ ์ • (Apex)', 'slope': '5-15ยฐ', 'sediment': '์—ญ (Gravel)'},
596
+ 2: {'name': '์„ ์•™ (Mid-fan)', 'slope': '2-5ยฐ', 'sediment': '์‚ฌ (Sand)'},
597
+ 3: {'name': '์„ ๋‹จ (Toe)', 'slope': '<2ยฐ', 'sediment': '๋‹ˆ (Silt)'}
598
+ }
599
+ }
600
+
601
  return elevation
602
 
603
 
604
+ def _get_fan_stage_desc(stage: float) -> str:
605
+ """์„ ์ƒ์ง€ ๋‹จ๊ณ„๋ณ„ ์„ค๋ช…"""
606
+ if stage < 0.3:
607
+ return "๐Ÿ”๏ธ ์„ ์ • ํ˜•์„ฑ: ํ˜‘๊ณก ์ถœ๊ตฌ์—์„œ ์œ ์† ๊ธ‰๊ฐ, ์—ญ ํ‡ด์  ์‹œ์ž‘"
608
+ elif stage < 0.6:
609
+ return "๐Ÿ“Š ์„ ์•™ ํ™•์žฅ: ์ˆ˜๋กœ ๋ถ„๊ธฐ, ์‚ฌ์งˆ ํ‡ด์ ๋ฌผ ํ™•์‚ฐ"
610
+ elif stage < 0.8:
611
+ return "๐ŸŒŠ ์„ ๋‹จ ๋ฐœ๋‹ฌ: ์„ธ๋ฆฝ์งˆ ํ‡ด์ , ๋ง๋‹จ๋ถ€ ์™„๋งŒํ•ด์ง"
612
+ else:
613
+ return "โœ… ์„ ์ƒ์ง€ ์™„์„ฑ: ์„ ์ •-์„ ์•™-์„ ๋‹จ ๋ถ„ํ™” ์™„๋ฃŒ"
614
+
615
+
616
  def create_meander_animated(grid_size: int, stage: float,
617
  amplitude: float = 0.3, num_bends: int = 3) -> np.ndarray:
618
  """๊ณก๋ฅ˜ ํ˜•์„ฑ๊ณผ์ • ์• ๋‹ˆ๋ฉ”์ด์…˜ (์ง์„  โ†’ ์‚ฌํ–‰ โ†’ ์šฐ๊ฐํ˜ธ โ†’ ํ•˜์ค‘๋„)
 
972
  # ============================================
973
 
974
  def create_incised_meander(grid_size: int = 100, stage: float = 1.0,
975
+ valley_depth: float = 80.0, num_terraces: int = 3,
976
+ return_metadata: bool = False) -> np.ndarray:
977
  """
978
  ๊ฐ์ž…๊ณก๋ฅ˜ (Incised Meander) + ํ•˜์•ˆ๋‹จ๊ตฌ (River Terraces)
979
 
980
+ Stage 0~0.3: ์ž์œ ๊ณก๋ฅ˜ (๋ฒ”๋žŒ์› ์œ„, ์นจ์‹๊ธฐ์ค€๋ฉด ๋†’์Œ)
981
+ Stage 0.3~0.7: ์œต๊ธฐ ์‹œ์ž‘ โ†’ ํ•˜๋ฐฉ์นจ์‹ ๊ฐ•ํ™”
982
+ Stage 0.7~1.0: ๊นŠ์€ ํ˜‘๊ณก + ํ•˜์•ˆ๋‹จ๊ตฌ ๋…ธ์ถœ
983
+
984
  ์œต๊ธฐ ํ™˜๊ฒฝ์—์„œ ๊ณก๋ฅ˜๊ฐ€ ์•”๋ฐ˜์„ ํŒŒ๊ณ  ๋“ค์–ด๊ฐ€๋ฉด์„œ ํ˜•์„ฑ
985
  """
986
  h, w = grid_size, grid_size
987
  elevation = np.zeros((h, w))
988
 
989
  center_x = w // 2
990
+ wl = h / 3 # 3๊ฐœ ๊ตฝ์ด
 
991
  channel_width = max(3, w // 25)
992
 
993
+ # ์นจ์‹๊ธฐ์ค€๋ฉด (stage์— ๋”ฐ๋ผ ํ•˜๊ฐ•)
994
+ # Stage 0: ๊ธฐ์ค€๋ฉด ๋†’์Œ (์ž์œ ๊ณก๋ฅ˜)
995
+ # Stage 1: ๊ธฐ์ค€๋ฉด ๋‚ฎ์Œ (๊ฐ์ž…)
996
+ base_level = valley_depth * (1 - stage * 0.9) # 90% ํ•˜๊ฐ•
997
+
998
+ # ๊ณก๋ฅ˜ ์ง„ํญ (stage์— ๋”ฐ๋ผ ๊ณ ์ •ํ™”)
999
+ if stage < 0.3:
1000
+ amplitude = w * 0.25 * (stage / 0.3) # ์‚ฌํ–‰ ๋ฐœ๋‹ฌ
1001
+ else:
1002
+ amplitude = w * 0.25 # ๊ณก๋ฅ˜ ํŒจํ„ด ๊ณ ์ •
1003
+
1004
+ # ๊ธฐ๋ฐ˜ ๊ณ ์› ๋†’์ด
1005
+ plateau_height = valley_depth
1006
+ elevation[:, :] = plateau_height
1007
+
1008
+ # ํ˜„์žฌ ์นจ์‹ ๊นŠ์ด (stage์— ๋”ฐ๋ผ ์ฆ๊ฐ€)
1009
+ current_depth = (plateau_height - base_level) * min(1.0, (stage - 0.2) / 0.8) if stage > 0.2 else 0
1010
 
1011
  # ๊ฐ์ž… ๊ณก๋ฅ˜ ํŒŒ๊ธฐ
1012
  for r in range(h):
 
1016
  for c in range(w):
1017
  dist = abs(c - meander_x)
1018
 
1019
+ # ํ•˜๋„ ๋ฐ”๋‹ฅ (์นจ์‹๊ธฐ์ค€๋ฉด๊นŒ์ง€)
1020
+ river_bottom = plateau_height - current_depth
1021
+
1022
  if dist < channel_width:
1023
  # ํ•˜๋„ (๊ฐ€์žฅ ๊นŠ์Œ)
1024
+ elevation[r, c] = max(base_level, river_bottom)
1025
+ elif dist < channel_width * 3:
1026
+ # ํ˜‘๊ณก ์ธก๋ฒฝ (V์žํ˜•)
1027
+ t = (dist - channel_width) / (channel_width * 2)
1028
+ elevation[r, c] = river_bottom + current_depth * t
 
 
 
1029
 
1030
+ # ํ•˜์•ˆ๋‹จ๊ตฌ (stage > 0.5์—์„œ ํ˜•์„ฑ)
1031
+ if stage > 0.5:
1032
+ terrace_progress = (stage - 0.5) / 0.5
1033
+ num_visible_terraces = int(num_terraces * terrace_progress) + 1
1034
+
1035
+ for t_idx in range(min(num_visible_terraces, num_terraces)):
1036
+ terrace_height = plateau_height - current_depth * (0.3 + 0.25 * t_idx)
1037
+ terrace_width_start = channel_width * (3 + t_idx)
1038
+ terrace_width_end = channel_width * (4 + t_idx)
1039
 
1040
+ for r in range(h):
1041
+ theta = 2 * np.pi * r / wl
1042
+ meander_x = center_x + amplitude * np.sin(theta) * (0.9 - 0.1 * t_idx)
1043
+
1044
+ for c in range(w):
1045
+ dist = abs(c - meander_x)
1046
+ if terrace_width_start < dist < terrace_width_end:
1047
+ if elevation[r, c] > terrace_height:
1048
+ elevation[r, c] = terrace_height
1049
+
1050
+ if return_metadata:
1051
+ return elevation, {
1052
+ 'base_level': base_level,
1053
+ 'current_depth': current_depth,
1054
+ 'stage_description': _get_incised_stage_desc(stage)
1055
+ }
1056
+
1057
  return elevation
1058
 
1059
 
1060
+ def _get_incised_stage_desc(stage: float) -> str:
1061
+ """๊ฐ์ž…๊ณก๋ฅ˜ ๋‹จ๊ณ„๋ณ„ ์„ค๋ช…"""
1062
+ if stage < 0.3:
1063
+ return "๐ŸŒŠ ์ž์œ ๊ณก๋ฅ˜ ๋‹จ๊ณ„: ๋ฒ”๋žŒ์› ์œ„๋ฅผ ์ž์œ ๋กญ๊ฒŒ ์‚ฌํ–‰"
1064
+ elif stage < 0.5:
1065
+ return "โฌ†๏ธ ์œต๊ธฐ ์‹œ์ž‘: ์นจ์‹๊ธฐ์ค€๋ฉด ํ•˜๊ฐ•, ํ•˜๋ฐฉ์นจ์‹ ์‹œ์ž‘"
1066
+ elif stage < 0.7:
1067
+ return "โ›๏ธ ๊ฐ์ž… ์ง„ํ–‰: ๊ณก๋ฅ˜ ํŒจํ„ด ๊ณ ์ •, ํ˜‘๊ณก ๊นŠ์–ด์ง"
1068
+ else:
1069
+ return "๐Ÿ”๏ธ ๊ฐ์ž…๊ณก๋ฅ˜ ์™„์„ฑ: ํ•˜์•ˆ๋‹จ๊ตฌ ํ˜•์„ฑ, ๊ณผ๊ฑฐ ํ•˜์ƒ ๋…ธ์ถœ"
1070
+
1071
+
1072
  def create_free_meander(grid_size: int = 100, stage: float = 1.0,
1073
+ num_bends: int = 4, return_metadata: bool = False) -> np.ndarray:
1074
  """
1075
  ์ž์œ ๊ณก๋ฅ˜ (Free Meander) + ๋ฒ”๋žŒ์› (Floodplain) + ์ž์—ฐ์ œ๋ฐฉ (Natural Levee)
1076
 
1077
+ Stage 0~0.2: ์ง์„  ํ•˜์ฒœ (์ดˆ๊ธฐ ํ•˜๋„)
1078
+ Stage 0.2~0.5: ์‚ฌํ–‰ ๋ฐœ๋‹ฌ (ํ—ฌ๋ฆฌ์ปฌ ํ๋ฆ„์— ์˜ํ•œ ๊ณต๊ฒฉ์‚ฌ๋ฉด ์นจ์‹)
1079
+ Stage 0.5~0.7: ๊ณก๋ฅ˜ ์ง„ํญ ์ฆ๊ฐ€ (์‚ฌํ–‰๋„ > 1.5)
1080
+ Stage 0.7~0.9: ๊ณก๋ฅ˜ ๋ชฉ ์ ˆ๋‹จ (Neck Cutoff) โ†’ ์šฐ๊ฐํ˜ธ ํ˜•์„ฑ
1081
+ Stage 0.9~1.0: ์ž์—ฐ์ œ๋ฐฉ ์™„์„ฑ + ๋ฐฐํ›„์Šต์ง€ ๋ถ„ํ™”
1082
+
1083
+ ํ—ฌ๋ฆฌ์ปฌ ํ๋ฆ„ (Helical Flow):
1084
+ - ๊ณก๋ฅ˜๋ถ€ ์™ธ์ธก: ์›์‹ฌ๋ ฅ โ†’ ์ˆ˜๋ฉด ์ƒ์Šน โ†’ ๋ฐ”๋‹ฅ์—์„œ ๋‚ด์ธก์œผ๋กœ ํšก๋ฅ˜
1085
+ - ๊ณต๊ฒฉ์‚ฌ๋ฉด(Cut Bank): ์นจ์‹
1086
+ - ํ™œ์ฃผ์‚ฌ๋ฉด(Point Bar): ํ‡ด์ 
1087
  """
1088
  h, w = grid_size, grid_size
1089
  elevation = np.zeros((h, w))
1090
 
1091
  # ๋ฒ”๋žŒ์› ๊ธฐ๋ฐ˜
1092
+ base_height = 10.0
1093
+ elevation[:, :] = base_height
1094
 
1095
  center_x = w // 2
 
 
1096
  channel_width = max(3, w // 20)
1097
+ wl = h / num_bends
1098
+
1099
+ # Stage์— ๋”ฐ๋ฅธ ์‚ฌํ–‰ ์ง„ํญ
1100
+ if stage < 0.2:
1101
+ amplitude = w * 0.05 # ๊ฑฐ์˜ ์ง์„ 
1102
+ else:
1103
+ amplitude = w * 0.3 * min(1.0, (stage - 0.1) / 0.4)
1104
+
1105
+ # ์‚ฌํ–‰๋„ ๊ณ„์‚ฐ
1106
+ sinuosity = 1.0 + amplitude / (h / num_bends) * 2
1107
+
1108
+ # ๊ณต๊ฒฉ์‚ฌ๋ฉด/ํ™œ์ฃผ์‚ฌ๋ฉด ์œ„์น˜ ์ €์žฅ
1109
+ cutbank_positions = []
1110
+ pointbar_positions = []
1111
 
1112
  for r in range(h):
1113
  theta = 2 * np.pi * r / wl
1114
  meander_x = center_x + amplitude * np.sin(theta)
1115
 
1116
+ # ๊ณก๋ฅ  ๋ฐฉํ–ฅ (๊ณต๊ฒฉ์‚ฌ๋ฉด ๊ฒฐ์ •์šฉ)
1117
+ curvature = np.cos(theta) # +: ์˜ค๋ฅธ์ชฝ ๊ณต๊ฒฉ์‚ฌ๋ฉด, -: ์™ผ์ชฝ ๊ณต๊ฒฉ์‚ฌ๋ฉด
1118
+
1119
  for c in range(w):
1120
+ dist = c - meander_x
1121
+ abs_dist = abs(dist)
1122
 
1123
+ if abs_dist < channel_width:
1124
+ # ํ•˜๋„ (๋น„๋Œ€์นญ ๋‹จ๋ฉด - stage ํ›„๋ฐ˜์—)
1125
+ if stage > 0.3:
1126
+ # ๊ณต๊ฒฉ์‚ฌ๋ฉด ์ชฝ์€ ๋” ๊นŠ์Œ
1127
+ if (curvature > 0 and dist > 0) or (curvature < 0 and dist < 0):
1128
+ depth_factor = 1.2 # ๊ณต๊ฒฉ์‚ฌ๋ฉด
1129
+ if r % 20 == 0:
1130
+ cutbank_positions.append((r, c))
1131
+ else:
1132
+ depth_factor = 0.7 # ํ™œ์ฃผ์‚ฌ๋ฉด
1133
+ if r % 20 == 0:
1134
+ pointbar_positions.append((r, c))
1135
+ else:
1136
+ depth_factor = 1.0
1137
+ elevation[r, c] = 5.0 - (channel_width - abs_dist) * 0.2 * depth_factor
1138
+
1139
+ elif abs_dist < channel_width * 2 and stage > 0.5:
1140
+ # ์ž์—ฐ์ œ๋ฐฉ (Levee) - stage ํ›„๋ฐ˜์— ๋ฐœ๋‹ฌ
1141
+ levee_height = base_height + 1.5 * ((stage - 0.5) / 0.5)
1142
+ elevation[r, c] = levee_height
1143
 
1144
+ elif abs_dist < channel_width * 5 and stage > 0.7:
1145
+ # ๋ฐฐํ›„์Šต์ง€ (Backswamp) - ์ž์—ฐ์ œ๋ฐฉ๋ณด๋‹ค ๋‚ฎ์Œ
1146
+ elevation[r, c] = base_height - 0.5
1147
+
1148
+ # ์šฐ๊ฐํ˜ธ (Oxbow Lake) - Stage 0.7 ์ดํ›„
1149
+ oxbow_formed = False
1150
  if stage > 0.7:
1151
+ oxbow_progress = (stage - 0.7) / 0.3
1152
  oxbow_y = h // 2
1153
+ oxbow_amplitude = amplitude * 1.4
1154
+
1155
  for dy in range(-int(wl/4), int(wl/4)):
1156
  r = oxbow_y + dy
1157
  if 0 <= r < h:
1158
  theta = 2 * np.pi * dy / (wl/2)
1159
+ ox_x = center_x + oxbow_amplitude * np.sin(theta)
1160
+
1161
+ for dc in range(-channel_width-2, channel_width + 3):
1162
  c = int(ox_x + dc)
1163
  if 0 <= c < w:
1164
+ # ์šฐ๊ฐํ˜ธ (๊ณ ๋ฆฝ๋œ ํ˜ธ์ˆ˜)
1165
+ elevation[r, c] = 4.0
1166
+ oxbow_formed = True
1167
+
1168
+ if return_metadata:
1169
+ return elevation, {
1170
+ 'sinuosity': sinuosity,
1171
+ 'amplitude': amplitude,
1172
+ 'cutbank_positions': cutbank_positions[:5], # ์ƒ์œ„ 5๊ฐœ
1173
+ 'pointbar_positions': pointbar_positions[:5],
1174
+ 'oxbow_formed': oxbow_formed,
1175
+ 'stage_description': _get_meander_stage_desc(stage)
1176
+ }
1177
+
1178
  return elevation
1179
 
1180
 
1181
+ def _get_meander_stage_desc(stage: float) -> str:
1182
+ """์ž์œ ๊ณก๋ฅ˜ ๋‹จ๊ณ„๋ณ„ ์„ค๋ช…"""
1183
+ if stage < 0.2:
1184
+ return "๐Ÿ“ ์ดˆ๊ธฐ ํ•˜๋„: ๊ฑฐ์˜ ์ง์„  ํ๋ฆ„"
1185
+ elif stage < 0.4:
1186
+ return "๐ŸŒ€ ์‚ฌํ–‰ ์‹œ์ž‘: ํ—ฌ๋ฆฌ์ปฌ ํ๋ฆ„์œผ๋กœ ๊ณต๊ฒฉ์‚ฌ๋ฉด ์นจ์‹ ์‹œ์ž‘"
1187
+ elif stage < 0.6:
1188
+ return "๐Ÿ”„ ๊ณก๋ฅ˜ ๋ฐœ๋‹ฌ: ์‚ฌํ–‰๋„ ์ฆ๊ฐ€, ํ™œ์ฃผ์‚ฌ๋ฉด ํ‡ด์ "
1189
+ elif stage < 0.8:
1190
+ return "โœ‚๏ธ ๋ชฉ ์ ˆ๋‹จ: ๊ณก๋ฅ˜ ๋ชฉ ๊ทผ์ ‘, ์šฐ๊ฐํ˜ธ ํ˜•์„ฑ ์‹œ์ž‘"
1191
+ else:
1192
+ return "๐Ÿž๏ธ ์„ฑ์ˆ™ ๊ณก๋ฅ˜: ์ž์—ฐ์ œ๋ฐฉ + ๋ฐฐํ›„์Šต์ง€ + ์šฐ๊ฐํ˜ธ ์™„์„ฑ"
1193
+
1194
+
1195
+ def create_bird_foot_delta(grid_size: int = 100, stage: float = 1.0,
1196
+ return_metadata: bool = False) -> np.ndarray:
1197
+ """์กฐ์กฑ์ƒ ์‚ผ๊ฐ์ฃผ (Bird-foot Delta) - ๋ฏธ์‹œ์‹œํ”ผ๊ฐ•ํ˜•
1198
+
1199
+ Stage 0~0.3: ์ฃผ ์ˆ˜๋กœ ํ˜•์„ฑ
1200
+ - ๋‹จ์ผ ํ•˜๋„๊ฐ€ ๋ฐ”๋‹ค๋กœ ์ง„์ž…
1201
+ - ์ดˆ๊ธฐ ํ‡ด์  ์‹œ์ž‘
1202
+
1203
+ Stage 0.3~0.6: ๋ถ„๋ฐฐ์ˆ˜๋กœ ๋ฐœ๋‹ฌ
1204
+ - ์ˆ˜๋กœ ๋ถ„๊ธฐ ์‹œ์ž‘
1205
+ - ๊ฐ ์ˆ˜๋กœ ์–‘์˜†์— ์ž์—ฐ์ œ๋ฐฉ ํ˜•์„ฑ
1206
+
1207
+ Stage 0.6~1.0: ์กฐ์กฑ์ƒ ์™„์„ฑ
1208
+ - ๋‹ค์ˆ˜์˜ ๋ถ„๋ฐฐ์ˆ˜๋กœ๊ฐ€ ์ƒˆ๋ฐœ ๋ชจ์–‘์œผ๋กœ ๋Œ์ถœ
1209
+ - ๊ฐ finger ๋์—์„œ ํ‡ด์  ํ™œ๋ฐœ
1210
+
1211
+ ํ˜•์„ฑ ์กฐ๊ฑด:
1212
+ - ํŒŒ๋ž‘ ์—๋„ˆ์ง€ ์•ฝํ•จ (๋งŒ ๋˜๋Š” ๋‚ดํ•ด)
1213
+ - ์กฐ์„ ์˜ํ–ฅ ์ ์Œ
1214
+ - ํ‡ด์ ๋ฌผ ๊ณต๊ธ‰ ํ’๋ถ€
1215
+
1216
+ ๋Œ€ํ‘œ ์‚ฌ๋ก€: ๋ฏธ์‹œ์‹œํ”ผ๊ฐ• ์‚ผ๊ฐ์ฃผ
1217
+ """
1218
  h, w = grid_size, grid_size
1219
  elevation = np.zeros((h, w))
1220
  elevation[:, :] = -5.0 # ๋ฐ”๋‹ค
1221
 
1222
+ apex_y = int(h * 0.12)
1223
  center_x = w // 2
1224
 
1225
+ # Stage์— ๋”ฐ๋ฅธ ๋ถ„๋ฐฐ์ˆ˜๋กœ ๊ฐœ์ˆ˜
1226
+ if stage < 0.3:
1227
+ num_fingers = 1
1228
+ elif stage < 0.5:
1229
+ num_fingers = 3
1230
+ else:
1231
+ num_fingers = min(7, 3 + int(4 * (stage - 0.5) / 0.5))
1232
+
1233
+ max_length = int((h - apex_y) * stage * 0.9)
1234
+ finger_width = max(3, int(4 * (1 - stage * 0.3))) # ์‹œ๊ฐ„์ด ๊ฐˆ์ˆ˜๋ก ์ข์•„์ง
1235
+
1236
+ distributary_info = []
1237
 
1238
  for i in range(num_fingers):
1239
+ # ๊ฐ๋„ ๋ถ„ํฌ (์ค‘์•™์—์„œ ์–‘์ชฝ์œผ๋กœ)
1240
+ if num_fingers == 1:
1241
+ angle = 0
1242
+ else:
1243
+ angle = np.radians(-35 + 70 * i / (num_fingers - 1))
1244
+
1245
+ finger_length = 0
1246
 
1247
  for d in range(max_length):
1248
  r = apex_y + int(d * np.cos(angle))
1249
  c = center_x + int(d * np.sin(angle))
1250
 
1251
  if 0 <= r < h and 0 <= c < w:
1252
+ finger_length = d
1253
+
1254
+ # ๋ถ„๋ฐฐ์ˆ˜๋กœ + ์ž์—ฐ์ œ๋ฐฉ
1255
+ for dc in range(-finger_width, finger_width + 1):
1256
  for dr in range(-2, 3):
1257
  nr, nc = r + dr, c + dc
1258
  if 0 <= nr < h and 0 <= nc < w:
1259
  dist = np.sqrt(dr**2 + dc**2)
 
 
1260
 
1261
+ # ์ค‘์•™: ์ˆ˜๋กœ (๋‚ฎ์Œ), ์–‘์ชฝ: ์ž์—ฐ์ œ๋ฐฉ (๋†’์Œ)
1262
+ if abs(dc) < 2:
1263
+ # ์ˆ˜๋กœ
1264
+ z = 2.0 * (1 - d / max_length) * stage
1265
+ else:
1266
+ # ์ž์—ฐ์ œ๋ฐฉ
1267
+ z = 6.0 * (1 - d / max_length) * (1 - (abs(dc) - 2) / finger_width) * stage
1268
+
1269
+ elevation[nr, nc] = max(elevation[nr, nc], z)
1270
+
1271
+ distributary_info.append({
1272
+ 'angle_deg': np.degrees(angle),
1273
+ 'length': finger_length
1274
+ })
1275
+
1276
+ # ์ƒ๋ฅ˜ ํ•˜์ฒœ
1277
  for r in range(apex_y):
1278
+ for dc in range(-4, 5):
1279
  if 0 <= center_x + dc < w:
1280
+ channel_depth = 3.0 * (1 - abs(dc) / 5)
1281
+ elevation[r, center_x + dc] = 5.0 + channel_depth
1282
+
1283
+ if return_metadata:
1284
+ return elevation, {
1285
+ 'num_distributaries': num_fingers,
1286
+ 'max_length': max_length,
1287
+ 'distributary_info': distributary_info,
1288
+ 'stage_description': _get_bird_foot_stage_desc(stage)
1289
+ }
1290
+
1291
  return elevation
1292
 
1293
 
1294
+ def _get_bird_foot_stage_desc(stage: float) -> str:
1295
+ """์กฐ์กฑ์ƒ ์‚ผ๊ฐ์ฃผ ๋‹จ๊ณ„๋ณ„ ์„ค๋ช…"""
1296
+ if stage < 0.2:
1297
+ return "๐Ÿž๏ธ ์ดˆ๊ธฐ: ๋‹จ์ผ ํ•˜๋„๊ฐ€ ๋ฐ”๋‹ค๋กœ ์ง„์ž…"
1298
+ elif stage < 0.4:
1299
+ return "๐ŸŒŠ ํ‡ด์  ์‹œ์ž‘: ํ•˜๊ตฌ์—์„œ ํ‡ด์ ๋ฌผ ์ถ•์ "
1300
+ elif stage < 0.6:
1301
+ return "๐Ÿ”€ ๋ถ„๊ธฐ ๋ฐœ์ƒ: ์ˆ˜๋กœ๊ฐ€ ์—ฌ๋Ÿฌ ๊ฐˆ๋ž˜๋กœ ๋‚˜๋‰จ"
1302
+ elif stage < 0.8:
1303
+ return "๐Ÿฆถ ์กฐ์กฑ์ƒ ๋ฐœ๋‹ฌ: ๊ฐ finger์— ์ž์—ฐ์ œ๋ฐฉ ํ˜•์„ฑ"
1304
+ else:
1305
+ return "๐Ÿฆ† ์กฐ์กฑ์ƒ ์™„์„ฑ: ์ƒˆ๋ฐœ ๋ชจ์–‘ ์‚ผ๊ฐ์ฃผ"
1306
+
1307
+
1308
  def create_arcuate_delta(grid_size: int = 100, stage: float = 1.0) -> np.ndarray:
1309
  """ํ˜ธ์ƒ ์‚ผ๊ฐ์ฃผ (Arcuate Delta) - ๋‚˜์ผ๊ฐ•ํ˜•"""
1310
  h, w = grid_size, grid_size
 
1374
 
1375
 
1376
  def create_cirque(grid_size: int = 100, stage: float = 1.0,
1377
+ depth: float = 50.0, return_metadata: bool = False) -> np.ndarray:
1378
+ """๊ถŒ๊ณก (Cirque) - ๋น™ํ•˜ ์‹œ์ž‘์ 
1379
+
1380
+ Stage 0~0.3: ๋‹ˆ๋ฐœ ์นจ์‹ (Nivation)
1381
+ - ๋งŒ๋…„์„ค์˜ ๋™๊ฒฐํ’ํ™”๋กœ ์–•์€ ์›€ํ‘น ํ˜•์„ฑ
1382
+ - ์•”์„ค(rock debris) ์ถ•์  ์‹œ์ž‘
1383
+
1384
+ Stage 0.3~0.6: ๋น™ํ•˜ํ™” (Glacierization)
1385
+ - ํ”ผ๋ฅธ(firn) โ†’ ๋น™ํ•˜ ์–ผ์Œ ์••๋ฐ€
1386
+ - ํ”Œ๋Ÿฌํ‚น(plucking)๊ณผ ๋งˆ์‹(abrasion) ์‹œ์ž‘
1387
+ - ํ›„๋ฒฝ(headwall) ๋ฐœ๋‹ฌ
1388
+
1389
+ Stage 0.6~0.8: ๊ถŒ๊ณก ํ™•์žฅ
1390
+ - ๋ฒ ๋ฅด๊ทธ์Šˆ๋ŸฐํŠธ(bergschrund) ๋™๊ฒฐํ’ํ™”
1391
+ - ๋ฐ”๋‹ฅ๊ณผ ๋ฒฝ ์นจ์‹ ํ™•๋Œ€
1392
+
1393
+ Stage 0.8~1.0: ๋น™ํ•˜ ์†Œ๋ฉธ + ํ„ด(Tarn) ํ˜•์„ฑ
1394
+ - ๋น™ํ•˜ ์œตํ•ด
1395
+ - ๋น™ํ•˜ํ˜ธ(tarn) ํ˜•์„ฑ
1396
+ """
1397
  h, w = grid_size, grid_size
1398
  elevation = np.zeros((h, w))
1399
 
1400
  # ์‚ฐ์•… ๋ฐฐ๊ฒฝ
1401
+ mountain_height = depth + 40.0
1402
+ elevation[:, :] = mountain_height
1403
 
1404
  # ๊ถŒ๊ณก ์œ„์น˜ (์ƒ๋‹จ ์ค‘์•™)
1405
+ cirque_y = int(h * 0.35)
1406
  cirque_x = w // 2
1407
+
1408
+ # Stage์— ๋”ฐ๋ฅธ ๊ถŒ๊ณก ํฌ๊ธฐ
1409
+ if stage < 0.3:
1410
+ # ๋‹ˆ๋ฐœ ์นจ์‹: ์ž‘์€ ์›€ํ‘น
1411
+ cirque_radius = int(w * 0.15 * (stage / 0.3))
1412
+ bowl_depth = depth * 0.3 * (stage / 0.3)
1413
+ else:
1414
+ # ๋น™ํ•˜ํ™” ์ดํ›„: ๊ธ‰๊ฒฉํžˆ ํ™•์žฅ
1415
+ cirque_radius = int(w * 0.15 + w * 0.15 * ((stage - 0.3) / 0.7))
1416
+ bowl_depth = depth * (0.3 + 0.7 * ((stage - 0.3) / 0.7))
1417
+
1418
+ # ํ›„๋ฒฝ ๊ฒฝ์‚ฌ๋„ (stage์— ๋”ฐ๋ผ ๊ธ‰ํ•ด์ง)
1419
+ headwall_steepness = 0.3 + 0.7 * min(1.0, stage / 0.6)
1420
 
1421
  for r in range(h):
1422
  for c in range(w):
 
1425
  dist = np.sqrt(dy**2 + dx**2)
1426
 
1427
  if dist < cirque_radius:
1428
+ # ๋ฐฉํ–ฅ์— ๋”ฐ๋ฅธ ๋†’์ด ๋ณ€ํ™”
1429
+ if dy < 0:
1430
+ # ํ›„๋ฒฝ (Headwall) - ๊ธ‰๊ฒฝ์‚ฌ
1431
+ wall_height = bowl_depth * headwall_steepness * (1 - dist / cirque_radius)
1432
+ elevation[r, c] = mountain_height - bowl_depth + wall_height
1433
+ else:
1434
+ # ๋ฐ”๋‹ฅ - ํ‰ํƒ„ํ•˜๊ฑฐ๋‚˜ ์•ฝ๊ฐ„ ์˜ค๋ชฉ
1435
+ floor_depth = bowl_depth * (1 - (dist / cirque_radius) ** 2) * 0.8
1436
+ elevation[r, c] = mountain_height - floor_depth
1437
+
1438
+ # ๋น™ํ•˜ ์œ ์ถœ๊ตฌ (ํ•˜๋ฅ˜ ๋ฐฉํ–ฅ)
1439
+ if cirque_y < r < cirque_y + cirque_radius * 0.5:
1440
+ if abs(c - cirque_x) < cirque_radius * 0.3:
1441
+ outlet_depth = bowl_depth * 0.5 * (1 - (r - cirque_y) / (cirque_radius * 0.5))
1442
+ elevation[r, c] = min(elevation[r, c], mountain_height - outlet_depth)
1443
+
1444
+ # ํ„ด(Tarn) ํ˜ธ์ˆ˜ - stage > 0.8
1445
+ tarn_present = False
1446
+ tarn_depth = 0
1447
+ if stage > 0.8:
1448
+ tarn_present = True
1449
+ tarn_progress = (stage - 0.8) / 0.2
1450
+ tarn_radius = int(cirque_radius * 0.5 * tarn_progress)
1451
+ tarn_depth = bowl_depth * 0.3 * tarn_progress
1452
+
1453
+ for r in range(cirque_y - tarn_radius, cirque_y + tarn_radius):
1454
+ for c in range(cirque_x - tarn_radius, cirque_x + tarn_radius):
1455
+ if 0 <= r < h and 0 <= c < w:
1456
+ dist = np.sqrt((r - cirque_y)**2 + (c - cirque_x)**2)
1457
+ if dist < tarn_radius:
1458
+ # ํ˜ธ์ˆ˜ ๋ฐ”๋‹ฅ
1459
+ elevation[r, c] = mountain_height - bowl_depth - tarn_depth * (1 - dist / tarn_radius)
1460
+
1461
+ if return_metadata:
1462
+ return elevation, {
1463
+ 'cirque_radius': cirque_radius,
1464
+ 'bowl_depth': bowl_depth,
1465
+ 'headwall_steepness': headwall_steepness,
1466
+ 'tarn_present': tarn_present,
1467
+ 'tarn_depth': tarn_depth if tarn_present else 0,
1468
+ 'stage_description': _get_cirque_stage_desc(stage)
1469
+ }
1470
+
1471
  return elevation
1472
 
1473
 
1474
+ def _get_cirque_stage_desc(stage: float) -> str:
1475
+ """๊ถŒ๊ณก ๋‹จ๊ณ„๋ณ„ ์„ค๋ช…"""
1476
+ if stage < 0.2:
1477
+ return "โ„๏ธ ๋‹ˆ๋ฐœ ์ดˆ๊ธฐ: ๋งŒ๋…„์„ค ์•„๋ž˜ ๋™๊ฒฐํ’ํ™” ์‹œ์ž‘"
1478
+ elif stage < 0.4:
1479
+ return "๐Ÿ”๏ธ ๋‹ˆ๋ฐœ ํ›„๊ธฐ: ์–•์€ ์›€ํ‘น, ์•”์„ค ์ถ•์ "
1480
+ elif stage < 0.6:
1481
+ return "๐ŸงŠ ๋น™ํ•˜ํ™”: ํ”ผ๋ฅธโ†’๋น™ํ•˜, ํ”Œ๋Ÿฌํ‚น+๋งˆ์‹ ์‹œ์ž‘"
1482
+ elif stage < 0.8:
1483
+ return "โ›๏ธ ๊ถŒ๊ณก ํ™•์žฅ: ๋ฒ ๋ฅด๊ทธ์Šˆ๋ŸฐํŠธ ๋™๊ฒฐํ’ํ™”"
1484
+ else:
1485
+ return "๐Ÿ’ง ํ„ด ํ˜•์„ฑ: ๋น™ํ•˜ ์†Œ๋ฉธ, ๋น™ํ•˜ํ˜ธ ํ˜•์„ฑ"
1486
+
1487
+
1488
+ def create_horn(grid_size: int = 100, stage: float = 1.0,
1489
+ num_cirques: int = 4, return_metadata: bool = False) -> np.ndarray:
1490
+ """ํ˜ธ๋ฅธ (Horn) - ํ”ผ๋ผ๋ฏธ๋“œํ˜• ๋ด‰์šฐ๋ฆฌ
1491
+
1492
+ Stage 0~0.3: ์ดˆ๊ธฐ ๊ถŒ๊ณก ํ˜•์„ฑ
1493
+ - ์—ฌ๋Ÿฌ ๋ฐฉํ–ฅ์—์„œ ๊ถŒ๊ณก ๋ฐœ๋‹ฌ ์‹œ์ž‘
1494
+ - ๋Šฅ์„  ํ˜•ํƒœ ์œ ์ง€
1495
+
1496
+ Stage 0.3~0.6: ๊ถŒ๊ณก ํ™•์žฅ
1497
+ - ๋‘๋ถ€์นจ์‹์œผ๋กœ ๊ถŒ๊ณก ๊นŠ์–ด์ง
1498
+ - ์•„๋ ˆํŠธ(arรชte) ๋ฐœ๋‹ฌ
1499
+
1500
+ Stage 0.6~0.9: ํ˜ธ๋ฅธ ํ˜•์„ฑ
1501
+ - ๊ถŒ๊ณก๋“ค์˜ ๋งŒ๋‚จ
1502
+ - ํ”ผ๋ผ๋ฏธ๋“œํ˜• ๋ด‰์šฐ๋ฆฌ ๋Œ์ถœ
1503
+
1504
+ Stage 0.9~1.0: ์„ฑ์ˆ™ ํ˜ธ๋ฅธ
1505
+ - ๋‚ ์นด๋กœ์šด ์ •์ƒ
1506
+ - ๋งˆํ„ฐํ˜ธ๋ฅธ ํ˜•ํƒœ
1507
+
1508
+ ๋Œ€ํ‘œ ์‚ฌ๋ก€: ๋งˆํ„ฐํ˜ธ๋ฅธ (์Šค์œ„์Šค), K2
1509
+ """
1510
  h, w = grid_size, grid_size
1511
  elevation = np.zeros((h, w))
1512
 
1513
  center = (h // 2, w // 2)
1514
+ max_peak_height = 120.0
1515
+
1516
+ # Stage์— ๋”ฐ๋ฅธ ๋ด‰์šฐ๋ฆฌ ๋†’์ด์™€ ๊ถŒ๊ณก ๊นŠ์ด
1517
+ if stage < 0.3:
1518
+ peak_height = max_peak_height * 0.6
1519
+ cirque_depth = 30.0 * (stage / 0.3)
1520
+ else:
1521
+ peak_height = max_peak_height * (0.6 + 0.4 * ((stage - 0.3) / 0.7))
1522
+ cirque_depth = 30.0 + 40.0 * ((stage - 0.3) / 0.7)
1523
 
1524
+ cirque_radius = int(w * 0.28 * (0.6 + 0.4 * stage))
 
 
1525
 
1526
+ # ๊ธฐ๋ณธ ์›๋ฟ”ํ˜• ์‚ฐ์ฒด
1527
  for r in range(h):
1528
  for c in range(w):
1529
  dy = r - center[0]
1530
  dx = c - center[1]
1531
  dist = np.sqrt(dy**2 + dx**2)
1532
 
1533
+ # ์›๋ฟ”ํ˜• ๊ธฐ๋ณธ ํ˜•ํƒœ
1534
+ elevation[r, c] = peak_height * max(0, 1 - dist / (w * 0.45))
1535
+
1536
+ # ๋‹ค๋ฐฉํ–ฅ ๊ถŒ๊ณก ํŒŒ๊ธฐ
1537
+ cirque_centers = []
1538
+ arete_count = 0
1539
+
1540
+ for i in range(num_cirques):
1541
+ angle = i * 2 * np.pi / num_cirques + np.pi / num_cirques # ์•ฝ๊ฐ„ ํšŒ์ „
1542
+ cx = center[1] + int(cirque_radius * 0.7 * np.cos(angle))
1543
+ cy = center[0] + int(cirque_radius * 0.7 * np.sin(angle))
1544
+ cirque_centers.append((cy, cx))
1545
+
1546
+ for r in range(h):
1547
+ for c in range(w):
1548
  cdist = np.sqrt((r - cy)**2 + (c - cx)**2)
1549
+
1550
  if cdist < cirque_radius * 0.6:
1551
+ # ๊ถŒ๊ณก ํŒŒ๊ธฐ (๋ฐ˜๊ทธ๋ฆ‡ ํ˜•ํƒœ)
1552
+ floor_height = 20.0 + cirque_depth * (cdist / (cirque_radius * 0.6)) ** 0.5
 
1553
 
1554
+ # ํ›„๋ฒฝ ๋ฐฉํ–ฅ (์ค‘์‹ฌ์ชฝ)์œผ๋กœ ๋” ๊ธ‰๊ฒฝ์‚ฌ
1555
+ dir_to_center = np.sqrt((r - center[0])**2 + (c - center[1])**2)
1556
+ if dir_to_center < cirque_radius * 0.5:
1557
+ floor_height += 20.0 * (1 - dir_to_center / (cirque_radius * 0.5))
1558
+
1559
+ elevation[r, c] = min(elevation[r, c], floor_height)
1560
+
1561
+ # ์•„๋ ˆํŠธ ๊ฐ•ํ™” (์ธ์ ‘ ๊ถŒ๊ณก ์‚ฌ์ด ๋Šฅ์„ )
1562
+ for i in range(num_cirques):
1563
+ next_i = (i + 1) % num_cirques
1564
+ cy1, cx1 = cirque_centers[i]
1565
+ cy2, cx2 = cirque_centers[next_i]
1566
+
1567
+ # ๋‘ ๊ถŒ๊ณก ์ค‘๊ฐ„์ 
1568
+ mid_y, mid_x = (cy1 + cy2) // 2, (cx1 + cx2) // 2
1569
+
1570
+ for r in range(h):
1571
+ for c in range(w):
1572
+ # ๋Šฅ์„  ๋ฐฉํ–ฅ์— ๊ฐ€๊นŒ์šด ํ”ฝ์…€์€ ๋†’์ด ์œ ์ง€
1573
+ dist_to_mid = np.sqrt((r - mid_y)**2 + (c - mid_x)**2)
1574
+ if dist_to_mid < cirque_radius * 0.3:
1575
+ dist_to_center = np.sqrt((r - center[0])**2 + (c - center[1])**2)
1576
+ if dist_to_center < cirque_radius * 0.5:
1577
+ ridge_boost = 15.0 * stage * (1 - dist_to_mid / (cirque_radius * 0.3))
1578
+ elevation[r, c] = min(elevation[r, c] + ridge_boost, peak_height)
1579
+
1580
+ if return_metadata:
1581
+ return elevation, {
1582
+ 'peak_height': peak_height,
1583
+ 'num_cirques': num_cirques,
1584
+ 'cirque_depth': cirque_depth,
1585
+ 'cirque_radius': cirque_radius,
1586
+ 'cirque_centers': cirque_centers,
1587
+ 'stage_description': _get_horn_stage_desc(stage)
1588
+ }
1589
+
1590
  return elevation
1591
 
1592
 
1593
+ def _get_horn_stage_desc(stage: float) -> str:
1594
+ """ํ˜ธ๋ฅธ ๋‹จ๊ณ„๋ณ„ ์„ค๋ช…"""
1595
+ if stage < 0.2:
1596
+ return "๐Ÿ”๏ธ ์ดˆ๊ธฐ ์‚ฐ์ฒด: ์›๋ฟ”ํ˜• ์‚ฐ, ๊ถŒ๊ณก ํ˜•์„ฑ ์‹œ์ž‘"
1597
+ elif stage < 0.4:
1598
+ return "โ„๏ธ ๊ถŒ๊ณก ๋ฐœ๋‹ฌ: ์—ฌ๋Ÿฌ ๋ฐฉํ–ฅ์—์„œ ๋น™ํ•˜ ์นจ์‹"
1599
+ elif stage < 0.6:
1600
+ return "โ›๏ธ ๋‘๋ถ€์นจ์‹: ๊ถŒ๊ณก ๊นŠ์–ด์ง, ์•„๋ ˆํŠธ ํ˜•์„ฑ"
1601
+ elif stage < 0.8:
1602
+ return "๐Ÿ—ป ํ˜ธ๋ฅธ ํ˜•์„ฑ: ๊ถŒ๊ณก ๋งŒ๋‚จ, ํ”ผ๋ผ๋ฏธ๋“œ ๋Œ์ถœ"
1603
+ else:
1604
+ return "โ›ฐ๏ธ ์„ฑ์ˆ™ ํ˜ธ๋ฅธ: ๋‚ ์นด๋กœ์šด ์ •์ƒ (๋งˆํ„ฐํ˜ธ๋ฅธํ˜•)"
1605
+
1606
+
1607
  def create_shield_volcano(grid_size: int = 100, stage: float = 1.0,
1608
  max_height: float = 40.0) -> np.ndarray:
1609
  """์ˆœ์ƒํ™”์‚ฐ (Shield Volcano) - ์™„๋งŒํ•œ ๊ฒฝ์‚ฌ"""
 
1759
  # ์ถ”๊ฐ€ ์ง€ํ˜• (Additional Landforms)
1760
  # ============================================
1761
 
1762
+ def create_fjord(grid_size: int = 100, stage: float = 1.0,
1763
+ return_metadata: bool = False) -> np.ndarray:
1764
  """ํ”ผ์˜ค๋ฅด๋“œ (Fjord) - ๋น™ํ•˜ ํ›„ํ‡ด ํ›„ ๋ฐ”๋‹ค ์œ ์ž…
1765
 
1766
  Stage 0.0~0.4: ๋น™ํ•˜๊ฐ€ U์ž๊ณก์„ ์ฑ„์›€ (๋น™ํ•˜๊ธฐ)
1767
+ - ๊ณ„๊ณก๋น™ํ•˜(Valley Glacier)๊ฐ€ U์ž๊ณก ์ ์œ 
1768
+ - ํ”Œ๋Ÿฌํ‚น(Plucking)๊ณผ ๋งˆ์‹(Abrasion)์œผ๋กœ ๋ฐ”๋‹ฅ ์นจ์‹
1769
+ - ๋น™ํ•˜ ๋‘๊ป˜ ์ˆ˜๋ฐฑm โ†’ ํ•ด์ˆ˜๋ฉด ์ดํ•˜๊นŒ์ง€ ์นจ์‹
1770
+
1771
+ Stage 0.4~0.7: ๋น™ํ•˜ ํ›„ํ‡ด ์‹œ์ž‘ (๊ฐ„๋น™๊ธฐ)
1772
+ - ๊ธฐํ›„ ์˜จ๋‚œํ™”๋กœ ๋น™ํ•˜ ์œตํ•ด
1773
+ - ๋น™ํ•˜ ๋ง๋‹จ์ด ์ƒ๋ฅ˜๋กœ ํ›„ํ‡ด
1774
+ - ํ•ด์ˆ˜๊ฐ€ ๋น™ํ•˜ ๋’ค๋ฅผ ๋”ฐ๋ผ ์œ ์ž…
1775
+
1776
+ Stage 0.7~1.0: ํ”ผ์˜ค๋ฅด๋“œ ์™„์„ฑ
1777
+ - ๋น™ํ•˜ ์™„์ „ ์†Œ๋ฉธ
1778
+ - ๊นŠ๊ณ  ์ข์€ ๋งŒ ํ˜•์„ฑ (์ˆ˜์‹ฌ ์ตœ๋Œ€ 1,300m)
1779
+ - ์ƒ๋ฅ˜๋กœ ๊ฐˆ์ˆ˜๋ก ์ˆ˜์‹ฌ ๊ฐ์†Œ
1780
  """
1781
  h, w = grid_size, grid_size
1782
  elevation = np.zeros((h, w))
1783
+ glacier_surface = None
1784
+ sea_surface = None
1785
 
1786
  # ์‚ฐ์•… ์ง€ํ˜• (๋†’์€ ์‚ฐ)
1787
  elevation[:, :] = 100.0
 
1790
  valley_width = int(w * 0.25)
1791
  valley_depth = 60.0
1792
 
1793
+ # U์ž๊ณก ํ˜•์„ฑ (๊ธฐ์ €)
1794
  for r in range(h):
1795
  for c in range(w):
1796
  dx = abs(c - center)
1797
 
1798
  if dx < valley_width:
1799
+ # U์ž ๋ฐ”๋‹ฅ (๋น™ํ•˜ ์นจ์‹์œผ๋กœ ๊นŠ์–ด์ง)
1800
+ # stage์— ๋”ฐ๋ผ ์นจ์‹ ๊นŠ์ด ์ฆ๊ฐ€
1801
+ erosion_depth = 10.0 - 50.0 * min(stage / 0.7, 1.0)
1802
+ elevation[r, c] = erosion_depth
1803
  elif dx < valley_width + 15:
1804
+ # U์ž ์ธก๋ฒฝ (๊ธ‰๊ฒฝ์‚ฌ)
1805
  t = (dx - valley_width) / 15
1806
+ base = 10.0 - 50.0 * min(stage / 0.7, 1.0) if dx == valley_width else 0
1807
+ elevation[r, c] = base + 100.0 * (t ** 0.5)
1808
 
1809
  # ๋น™ํ•˜ / ๋ฐ”๋‹ค ์ƒํƒœ
1810
  if stage < 0.4:
1811
  # ๋น™ํ•˜๊ธฐ: U์ž๊ณก์— ๋น™ํ•˜ ์ฑ„์›€
1812
+ glacier_extent = int(h * (0.95 - stage * 0.5))
1813
+ glacier_thickness = 60.0 * (1 - stage * 0.3)
1814
+
1815
+ glacier_surface = np.full((h, w), np.nan)
1816
 
1817
  for r in range(glacier_extent):
1818
  for c in range(w):
1819
  dx = abs(c - center)
1820
  if dx < valley_width:
1821
+ # ๋น™ํ•˜ ํ‘œ๋ฉด (๋ณผ๋ก, ์ค‘์•™ ๋‘๊บผ์›€)
1822
  cross_profile = glacier_thickness * (1 - (dx / valley_width) ** 2)
1823
+ glacier_surface[r, c] = elevation[r, c] + cross_profile
1824
 
1825
  elif stage < 0.7:
1826
+ # ๋น™ํ•˜ ํ›„ํ‡ด ์ค‘
1827
  retreat_factor = (stage - 0.4) / 0.3
1828
 
1829
  # ๋น™ํ•˜ ์ž”๋ฅ˜ (์ƒ๋ฅ˜์—๋งŒ)
1830
+ glacier_end = int(h * (0.8 - 0.6 * retreat_factor))
1831
+ glacier_thickness = 50.0 * (1 - retreat_factor * 0.7)
1832
+
1833
+ glacier_surface = np.full((h, w), np.nan)
1834
+ sea_surface = np.full((h, w), np.nan)
1835
 
1836
  for r in range(glacier_end):
1837
  for c in range(w):
1838
  dx = abs(c - center)
1839
  if dx < valley_width:
1840
  cross_profile = glacier_thickness * (1 - (dx / valley_width) ** 2)
1841
+ glacier_surface[r, c] = elevation[r, c] + cross_profile
1842
 
1843
  # ๋ฐ”๋‹ค ์œ ์ž… (ํ•˜๋ฅ˜๋ถ€ํ„ฐ)
1844
+ for r in range(glacier_end, h):
 
1845
  for c in range(w):
1846
  dx = abs(c - center)
1847
  if dx < valley_width:
1848
+ sea_surface[r, c] = 0 # ํ•ด์ˆ˜๋ฉด
1849
+
1850
  else:
1851
+ # ํ”ผ์˜ค๋ฅด๋“œ ์™„์„ฑ
1852
+ sea_surface = np.full((h, w), np.nan)
1853
 
1854
  for r in range(h):
1855
  for c in range(w):
1856
  dx = abs(c - center)
1857
  if dx < valley_width:
1858
+ sea_surface[r, c] = 0 # ํ•ด์ˆ˜๋ฉด
1859
+
1860
+ if return_metadata:
1861
+ return elevation, {
1862
+ 'glacier_surface': glacier_surface,
1863
+ 'sea_surface': sea_surface,
1864
+ 'stage_description': _get_fjord_stage_desc(stage),
1865
+ 'glacier_extent': glacier_extent if stage < 0.4 else (glacier_end if stage < 0.7 else 0),
1866
+ 'process_info': {
1867
+ 'plucking': '๋น™ํ•˜๊ฐ€ ๊ธฐ๋ฐ˜์•”์— ๋™๊ฒฐ ๋ถ€์ฐฉ ํ›„ ๋–ผ์–ด๋ƒ„',
1868
+ 'abrasion': '๋น™ํ•˜ ๋ฐ”๋‹ฅ์˜ ์•”์„ ํŒŒํŽธ์ด ๊ธฐ๋ฐ˜์•”์„ ๊นŽ์Œ',
1869
+ 'overdeepening': '๋น™ํ•˜ ์นจ์‹์ด ํ•ด์ˆ˜๋ฉด ์•„๋ž˜๊นŒ์ง€ ์ง„ํ–‰'
1870
+ }
1871
+ }
1872
+
1873
  return elevation
1874
 
1875
 
1876
+ def _get_fjord_stage_desc(stage: float) -> str:
1877
+ """ํ”ผ์˜ค๋ฅด๋“œ ๋‹จ๊ณ„๋ณ„ ์„ค๋ช…"""
1878
+ if stage < 0.2:
1879
+ return "โ„๏ธ ๋น™ํ•˜๊ธฐ ์ดˆ๊ธฐ: ๋น™ํ•˜๊ฐ€ U์ž๊ณก์„ ์ฑ„์šฐ๊ธฐ ์‹œ์ž‘"
1880
+ elif stage < 0.4:
1881
+ return "๐Ÿ”๏ธ ๋น™ํ•˜๊ธฐ ์ตœ์„ฑ๊ธฐ: ํ”Œ๋Ÿฌํ‚น๊ณผ ๋งˆ์‹์œผ๋กœ ๋ฐ”๋‹ฅ ๊นŠ์–ด์ง"
1882
+ elif stage < 0.55:
1883
+ return "๐ŸŒก๏ธ ๊ฐ„๋น™๊ธฐ ์‹œ์ž‘: ์˜จ๋‚œํ™”๋กœ ๋น™ํ•˜ ์œตํ•ด ์‹œ์ž‘"
1884
+ elif stage < 0.7:
1885
+ return "๐ŸŒŠ ํ•ด์นจ ์ง„ํ–‰: ๋น™ํ•˜ ํ›„ํ‡ด, ํ•ด์ˆ˜ ์œ ์ž…"
1886
+ else:
1887
+ return "๐ŸŒ… ํ”ผ์˜ค๋ฅด๋“œ ์™„์„ฑ: ๊นŠ๊ณ  ์ข์€ ๋งŒ ํ˜•์„ฑ"
1888
+
1889
+
1890
  def create_drumlin(grid_size: int = 100, stage: float = 1.0,
1891
  num_drumlins: int = 5) -> np.ndarray:
1892
  """๋“œ๋Ÿผ๋ฆฐ (Drumlin) - ๋น™ํ•˜ ๋ฐฉํ–ฅ ํƒ€์›ํ˜• ์–ธ๋•"""
 
2007
 
2008
 
2009
  def create_waterfall(grid_size: int = 100, stage: float = 1.0,
2010
+ drop_height: float = 50.0, return_metadata: bool = False) -> np.ndarray:
2011
  """ํญํฌ (Waterfall) - ๋‘๋ถ€์นจ์‹์œผ๋กœ ํ›„ํ‡ด
2012
 
2013
+ Stage 0.0~0.3: ํญํฌ ํ˜•์„ฑ (ํ•˜๋ฅ˜์—์„œ ์‹œ์ž‘)
2014
+ Stage 0.3~0.7: ๋‘๋ถ€์นจ์‹ ์ง„ํ–‰ (์ƒ๋ฅ˜๋กœ ํ›„ํ‡ด)
2015
+ Stage 0.7~1.0: ํ˜‘๊ณก ๋ฐœ๋‹ฌ (๊นŠ์€ ๊ณก์ €)
2016
+
2017
+ ์ฐจ๋ณ„์นจ์‹ (Differential Erosion):
2018
+ - ๊ฒฝ์•”์ธต(hard rock): ์นจ์‹์— ๊ฐ•ํ•จ โ†’ ํญํฌ ์ ˆ๋ฒฝ ํ˜•์„ฑ
2019
+ - ์—ฐ์•”์ธต(soft rock): ์นจ์‹์— ์•ฝํ•จ โ†’ ์–ธ๋”์ปทํŒ… โ†’ ๋ถ•๊ดด
2020
+
2021
+ ํ”Œ๋Ÿฐ์ง€ํ’€(Plunge Pool):
2022
+ - ๋‚™ํ•˜์ˆ˜์˜ ์ถฉ๊ฒฉ์œผ๋กœ ๋ฐ”๋‹ฅ ์นจ์‹
2023
+ - ์™€๋ฅ˜(vortex)์— ์˜ํ•œ ํฌํŠธํ™€ ํ˜•์„ฑ
2024
  """
2025
  h, w = grid_size, grid_size
2026
  elevation = np.zeros((h, w))
2027
  center = w // 2
2028
 
2029
  # ํญํฌ ์œ„์น˜ (stage์— ๋”ฐ๋ผ ์ƒ๋ฅ˜๋กœ ํ›„ํ‡ด)
2030
+ initial_fall = int(h * 0.75)
2031
+ final_fall = int(h * 0.25)
2032
+ retreat_distance = (initial_fall - final_fall) * stage
2033
+ fall_r = int(initial_fall - retreat_distance)
2034
 
2035
+ # ์ƒ๋ฅ˜ (๋†’์€ ๊ณ ์› - ๊ฒฝ์•”์ธต)
2036
  hard_rock_height = drop_height + 30.0
2037
+
2038
  for r in range(fall_r):
2039
  for c in range(w):
2040
  # ์ƒ๋ฅ˜๋กœ ๊ฐˆ์ˆ˜๋ก ๋†’์•„์ง
2041
  upstream_rise = (fall_r - r) * 0.3
2042
  elevation[r, c] = hard_rock_height + upstream_rise
2043
 
2044
+ # ํญํฌ ์ ˆ๋ฒฝ (๊ฑฐ์˜ ์ˆ˜์ง)
2045
+ cliff_width = max(3, int(5 * stage))
2046
  for r in range(fall_r, min(fall_r + cliff_width, h)):
2047
  for c in range(w):
2048
  t = (r - fall_r) / cliff_width
2049
+ # ์ˆ˜์ง์— ๊ฐ€๊นŒ์šด ๋‚™ํ•˜
2050
+ elevation[r, c] = hard_rock_height * (1 - t**0.5) + 10.0 * t**0.5
2051
 
2052
+ # ํ•˜๋ฅ˜ (์—ฐ์•”์ธต - ์นจ์‹๋จ)
2053
  for r in range(fall_r + cliff_width, h):
2054
  for c in range(w):
2055
+ downstream_drop = (r - fall_r - cliff_width) * 0.15
 
2056
  elevation[r, c] = 10.0 - downstream_drop
2057
 
2058
+ # ํ˜‘๊ณก (ํญํฌ ํ›„ํ‡ด ๊ฒฝ๋กœ) - stage์— ๋”ฐ๋ผ ๋ฐœ๋‹ฌ
2059
  gorge_start = fall_r + cliff_width
2060
+ gorge_end = initial_fall + 10
2061
+ gorge_depth = 10.0 * stage
2062
+ gorge_width = int(6 + 4 * stage)
2063
 
2064
  for r in range(gorge_start, min(gorge_end, h)):
2065
+ for dc in range(-gorge_width, gorge_width + 1):
2066
  c = center + dc
2067
  if 0 <= c < w:
2068
  # V์ž ํ˜‘๊ณก ๋‹จ๋ฉด
2069
+ depth = gorge_depth * (1 - abs(dc) / gorge_width)
2070
  elevation[r, c] -= depth
2071
 
2072
  # ํ•˜์ฒœ ์ˆ˜๋กœ
2073
+ channel_width = 4
2074
  for r in range(h):
2075
+ for dc in range(-channel_width, channel_width + 1):
2076
  c = center + dc
2077
  if 0 <= c < w:
2078
+ channel_depth = 3.0 * (1 - abs(dc) / channel_width)
2079
+ elevation[r, c] -= channel_depth
2080
+
2081
+ # ํ”Œ๋Ÿฐ์ง€ํ’€ (ํญํ˜ธ)
2082
+ pool_r = fall_r + cliff_width + 3
2083
+ pool_depth = 12.0 + 5.0 * stage
2084
+ pool_radius = 8
2085
+
2086
+ for dr in range(-pool_radius, pool_radius + 1):
2087
+ for dc in range(-pool_radius, pool_radius + 1):
2088
+ r_pos, c_pos = pool_r + dr, center + dc
2089
+ if 0 <= r_pos < h and 0 <= c_pos < w:
2090
  dist = np.sqrt(dr**2 + dc**2)
2091
+ if dist < pool_radius:
2092
+ pool_effect = pool_depth * (1 - (dist / pool_radius)**2)
2093
+ elevation[r_pos, c_pos] = min(elevation[r_pos, c_pos], 5.0 - pool_effect)
2094
+
2095
+ if return_metadata:
2096
+ return elevation, {
2097
+ 'waterfall_position': fall_r,
2098
+ 'retreat_distance': retreat_distance,
2099
+ 'gorge_length': gorge_end - gorge_start if stage > 0.3 else 0,
2100
+ 'plunge_pool_depth': pool_depth,
2101
+ 'layer_info': {
2102
+ 'hard_rock': {'height': hard_rock_height, 'description': '๊ฒฝ์•”์ธต (์ €ํ•ญ์„ฑ ๋†’์Œ)'},
2103
+ 'soft_rock': {'height': 20, 'description': '์—ฐ์•”์ธต (์นจ์‹์— ์•ฝํ•จ)'}
2104
+ },
2105
+ 'stage_description': _get_waterfall_stage_desc(stage)
2106
+ }
2107
 
2108
  return elevation
2109
 
2110
 
2111
+ def _get_waterfall_stage_desc(stage: float) -> str:
2112
+ """ํญํฌ ๋‹จ๊ณ„๋ณ„ ์„ค๋ช…"""
2113
+ if stage < 0.2:
2114
+ return "๐Ÿž๏ธ ํญํฌ ํ˜•์„ฑ: ๊ฒฝ์•”-์—ฐ์•” ๊ฒฝ๊ณ„์—์„œ ์ฐจ๋ณ„์นจ์‹ ์‹œ์ž‘"
2115
+ elif stage < 0.4:
2116
+ return "๐Ÿ’ง ํ”Œ๋Ÿฐ์ง€ํ’€ ๋ฐœ๋‹ฌ: ๋‚™ํ•˜์ˆ˜ ์ถฉ๊ฒฉ์œผ๋กœ ํญํ˜ธ ํ˜•์„ฑ"
2117
+ elif stage < 0.6:
2118
+ return "โ›๏ธ ๋‘๋ถ€์นจ์‹ ์ง„ํ–‰: ์—ฐ์•” ์–ธ๋”์ปทํŒ… โ†’ ๊ฒฝ์•” ๋ถ•๊ดด"
2119
+ elif stage < 0.8:
2120
+ return "๐Ÿ”๏ธ ํญํฌ ํ›„ํ‡ด: ์ƒ๋ฅ˜๋กœ ์ด๋™, ํ˜‘๊ณก ์—ฐ์žฅ"
2121
+ else:
2122
+ return "๐Ÿ—ป ์„ฑ์ˆ™ ํญํฌ: ๊นŠ์€ ํ˜‘๊ณก + ๋„“์€ ํ”Œ๋Ÿฐ์ง€ํ’€"
2123
+
2124
+
2125
  def create_karst_doline(grid_size: int = 100, stage: float = 1.0,
2126
  num_dolines: int = 5) -> np.ndarray:
2127
  """๋Œ๋ฆฌ๋„ค (Doline/Sinkhole) - ์นด๋ฅด์ŠคํŠธ ์ง€ํ˜•"""
pages/1_๐Ÿ“–_Gallery.py CHANGED
@@ -13,7 +13,7 @@ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
13
  sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
14
 
15
  from engine.ideal_landforms import IDEAL_LANDFORM_GENERATORS, ANIMATED_LANDFORM_GENERATORS
16
- from app.main import render_terrain_plotly
17
 
18
  st.header("๐Ÿ“– ์ด์ƒ์  ์ง€ํ˜• ๊ฐค๋Ÿฌ๋ฆฌ")
19
  st.markdown("_๊ต๊ณผ์„œ์ ์ธ ์ง€ํ˜• ํ˜•ํƒœ๋ฅผ ๊ธฐํ•˜ํ•™์  ๋ชจ๋ธ๋กœ ์‹œ๊ฐํ™”ํ•ฉ๋‹ˆ๋‹ค._")
@@ -225,7 +225,75 @@ if landform_key in ANIMATED_LANDFORM_GENERATORS:
225
  )
226
 
227
  anim_func = ANIMATED_LANDFORM_GENERATORS[landform_key]
228
- stage_elev = anim_func(gallery_grid_size, stage_value)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
229
 
230
  # ๋ฌผ ์ƒ์„ฑ
231
  stage_water = np.maximum(0, -stage_elev + 1.0)
 
13
  sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
14
 
15
  from engine.ideal_landforms import IDEAL_LANDFORM_GENERATORS, ANIMATED_LANDFORM_GENERATORS
16
+ from app.components.renderer import render_terrain_plotly
17
 
18
  st.header("๐Ÿ“– ์ด์ƒ์  ์ง€ํ˜• ๊ฐค๋Ÿฌ๋ฆฌ")
19
  st.markdown("_๊ต๊ณผ์„œ์ ์ธ ์ง€ํ˜• ํ˜•ํƒœ๋ฅผ ๊ธฐํ•˜ํ•™์  ๋ชจ๋ธ๋กœ ์‹œ๊ฐํ™”ํ•ฉ๋‹ˆ๋‹ค._")
 
225
  )
226
 
227
  anim_func = ANIMATED_LANDFORM_GENERATORS[landform_key]
228
+
229
+ # ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์ง€์› ์ง€ํ˜• ํ™•์ธ
230
+ supported_metadata = [
231
+ 'incised_meander', 'alluvial_fan', 'fjord', # ๊ธฐ์กด
232
+ 'free_meander', 'waterfall', 'cirque', 'horn', 'coastal_cliff', # ์‹ ๊ทœ
233
+ 'bird_foot_delta' # ์ถ”๊ฐ€
234
+ ]
235
+
236
+ if landform_key in supported_metadata:
237
+ try:
238
+ stage_elev, metadata = anim_func(gallery_grid_size, stage_value, return_metadata=True)
239
+ # ๋‹จ๊ณ„๋ณ„ ์„ค๋ช… ํ‘œ์‹œ
240
+ st.success(metadata.get('stage_description', ''))
241
+
242
+ # ์„ ์ƒ์ง€ ์กด ์ •๋ณด
243
+ if landform_key == 'alluvial_fan' and 'zone_info' in metadata:
244
+ with st.expander("๐Ÿ“Š ์„ธ๋ถ€ ๊ตฌ์กฐ ๋ณด๊ธฐ"):
245
+ for zone_id, info in metadata['zone_info'].items():
246
+ st.markdown(f"**{info['name']}**: ๊ฒฝ์‚ฌ {info['slope']}, {info['sediment']}")
247
+
248
+ # ํ”ผ์˜ค๋ฅด๋“œ ํ”„๋กœ์„ธ์Šค ์ •๋ณด
249
+ if landform_key == 'fjord' and 'process_info' in metadata:
250
+ with st.expander("๐ŸงŠ ๋น™ํ•˜ ์ž‘์šฉ ๋ณด๊ธฐ"):
251
+ for process, desc in metadata['process_info'].items():
252
+ st.markdown(f"- **{process}**: {desc}")
253
+
254
+ # ์ž์œ ๊ณก๋ฅ˜ ์ •๋ณด
255
+ if landform_key == 'free_meander':
256
+ with st.expander("๐ŸŒ€ ๊ณก๋ฅ˜ ์ •๋ณด ๋ณด๊ธฐ"):
257
+ st.markdown(f"**์‚ฌํ–‰๋„**: {metadata.get('sinuosity', 1):.2f}")
258
+ st.markdown(f"**์šฐ๊ฐํ˜ธ ํ˜•์„ฑ**: {'โœ… ์˜ˆ' if metadata.get('oxbow_formed', False) else 'โŒ ์•„๋‹ˆ์˜ค'}")
259
+
260
+ # ํญํฌ ์ •๋ณด
261
+ if landform_key == 'waterfall' and 'layer_info' in metadata:
262
+ with st.expander("โ›ฐ๏ธ ์ฐจ๋ณ„์นจ์‹ ๋ณด๊ธฐ"):
263
+ for layer, info in metadata['layer_info'].items():
264
+ st.markdown(f"- **{layer}**: {info['description']}")
265
+ st.markdown(f"**ํ›„ํ‡ด ๊ฑฐ๋ฆฌ**: {metadata.get('retreat_distance', 0):.0f}m")
266
+
267
+ # ๊ถŒ๊ณก ์ •๋ณด
268
+ if landform_key == 'cirque':
269
+ with st.expander("โ„๏ธ ๋น™ํ•˜ ์นจ์‹ ๋ณด๊ธฐ"):
270
+ st.markdown(f"**๊ถŒ๊ณก ๋ฐ˜๊ฒฝ**: {metadata.get('cirque_radius', 0)}m")
271
+ st.markdown(f"**ํ„ด(ํ˜ธ์ˆ˜) ํ˜•์„ฑ**: {'โœ… ์˜ˆ' if metadata.get('tarn_present', False) else 'โŒ ์•„๋‹ˆ์˜ค'}")
272
+
273
+ # ํ˜ธ๋ฅธ ์ •๋ณด
274
+ if landform_key == 'horn':
275
+ with st.expander("๐Ÿ—ป ๋‹ค์ค‘ ๊ถŒ๊ณก ๋ณด๊ธฐ"):
276
+ st.markdown(f"**๊ถŒ๊ณก ๊ฐœ์ˆ˜**: {metadata.get('num_cirques', 0)}๊ฐœ")
277
+ st.markdown(f"**์ •์ƒ ๋†’์ด**: {metadata.get('peak_height', 0):.0f}m")
278
+
279
+ # ํ•ด์•ˆ์ ˆ๋ฒฝ ์ •๋ณด
280
+ if landform_key == 'coastal_cliff' and 'erosion_processes' in metadata:
281
+ with st.expander("๐ŸŒŠ ํŒŒ๋ž‘ ์นจ์‹ ๋ณด๊ธฐ"):
282
+ for process, desc in metadata['erosion_processes'].items():
283
+ st.markdown(f"- **{process}**: {desc}")
284
+ st.markdown(f"**ํ›„ํ‡ด๋Ÿ‰**: {metadata.get('retreat_amount', 0)}m")
285
+
286
+ # ์กฐ์กฑ์ƒ ์‚ผ๊ฐ์ฃผ ์ •๋ณด
287
+ if landform_key == 'bird_foot_delta':
288
+ with st.expander("๐Ÿฆถ ๋ถ„๋ฐฐ์ˆ˜๋กœ ๋ณด๊ธฐ"):
289
+ st.markdown(f"**๋ถ„๋ฐฐ์ˆ˜๋กœ ๊ฐœ์ˆ˜**: {metadata.get('num_distributaries', 0)}๊ฐœ")
290
+ st.markdown(f"**์ตœ๋Œ€ ๊ธธ์ด**: {metadata.get('max_length', 0)}m")
291
+
292
+ except TypeError:
293
+ # return_metadata ์ง€์› ์•ˆ ํ•˜๋Š” ๊ฒฝ์šฐ
294
+ stage_elev = anim_func(gallery_grid_size, stage_value)
295
+ else:
296
+ stage_elev = anim_func(gallery_grid_size, stage_value)
297
 
298
  # ๋ฌผ ์ƒ์„ฑ
299
  stage_water = np.maximum(0, -stage_elev + 1.0)