""" ๐ŸŽจ Terrain Renderer - Plotly 3D ์‹œ๊ฐํ™” ๋ถ„๋ฆฌ๋œ ๋ชจ๋“ˆ๋กœ HuggingFace Spaces ํ˜ธํ™˜์„ฑ ํ–ฅ์ƒ """ import numpy as np import plotly.graph_objects as go import os try: from PIL import Image except ImportError: Image = None def render_terrain_plotly(elevation, title, add_water=True, water_level=0, texture_path=None, force_camera=True, water_depth_grid=None, sediment_grid=None, landform_type=None): """Plotly ์ธํ„ฐ๋ž™ํ‹ฐ๋ธŒ 3D Surface - ์‚ฌ์‹ค์  ํ…์Šค์ฒ˜(Biome) ์ ์šฉ Args: elevation: 2D numpy array - ๊ณ ๋„ ๋ฐ์ดํ„ฐ title: ๊ทธ๋ž˜ํ”„ ์ œ๋ชฉ add_water: ๋ฌผ ํ‘œ๋ฉด ์ถ”๊ฐ€ ์—ฌ๋ถ€ water_level: ํ•ด์ˆ˜๋ฉด ๋†’์ด texture_path: ํ…์Šค์ฒ˜ ์ด๋ฏธ์ง€ ๊ฒฝ๋กœ force_camera: ์นด๋ฉ”๋ผ ์œ„์น˜ ๊ณ ์ • ์—ฌ๋ถ€ water_depth_grid: ๋ฌผ ๊นŠ์ด ๊ทธ๋ฆฌ๋“œ sediment_grid: ํ‡ด์ ๋ฌผ ๊ทธ๋ฆฌ๋“œ landform_type: 'river', 'coastal', 'glacial', 'volcanic', 'karst', 'arid' """ h, w = elevation.shape x = np.arange(w) y = np.arange(h) # ๊ฒฝ์‚ฌ๋„ ๊ณ„์‚ฐ dy, dx = np.gradient(elevation) slope = np.sqrt(dx**2 + dy**2) # Biome Index (0: ๋ฌผ/๋ชจ๋ž˜, 1: ํ’€, 2: ์•”์„, 3: ๋ˆˆ) biome = np.zeros_like(elevation) biome[:] = 1 # ๊ธฐ๋ณธ: ํ’€ # ํ‡ด์ ์ง€ ํŒ๋ณ„ sand_level = water_level + 5 if add_water else elevation.min() + 10 is_deposit = np.zeros_like(elevation, dtype=bool) if sediment_grid is not None: is_deposit = (sediment_grid > 0.5) else: is_deposit = (elevation < sand_level) & (slope < 0.5) biome[is_deposit] = 0 # ์•”์„ (๊ฒฝ์‚ฌ๊ฐ€ ๊ธ‰ํ•œ ๊ณณ) biome[slope > 1.2] = 2 # ์ง€ํ˜• ์œ ํ˜•๋ณ„ ์ฒ˜๋ฆฌ if landform_type == 'glacial': biome[elevation > 50] = 3 biome[slope > 1.5] = 2 elif landform_type in ['river', 'coastal']: if water_depth_grid is not None: is_water = water_depth_grid > 0.5 biome[is_water] = 0 biome[elevation < 0] = 0 elif landform_type == 'arid': biome[slope < 0.8] = 0 # ๋…ธ์ด์ฆˆ ์ถ”๊ฐ€ noise = np.random.normal(0, 0.2, elevation.shape) biome_noisy = np.clip(biome + noise, 0, 3).round(2) # ์ปฌ๋Ÿฌ์Šค์ผ€์ผ ์„ค์ • if landform_type == 'glacial': realistic_colorscale = [ [0.0, '#E6C288'], [0.25, '#E6C288'], [0.25, '#556B2F'], [0.5, '#556B2F'], [0.5, '#808080'], [0.75, '#808080'], [0.75, '#E0FFFF'], [1.0, '#FFFFFF'] ] colorbar_labels = ["ํ‡ด์ (ๅœŸ)", "์‹์ƒ(่‰)", "์•”์„(ๅฒฉ)", "๋น™ํ•˜(ๆฐท)"] elif landform_type in ['river', 'coastal']: realistic_colorscale = [ [0.0, '#4682B4'], [0.25, '#4682B4'], [0.25, '#556B2F'], [0.5, '#556B2F'], [0.5, '#808080'], [0.75, '#808080'], [0.75, '#D2B48C'], [1.0, '#D2B48C'] ] colorbar_labels = ["์ˆ˜์—ญ(ๆฐด)", "์‹์ƒ(่‰)", "์•”์„(ๅฒฉ)", "์‚ฌ์งˆ(็ ‚)"] elif landform_type == 'arid': realistic_colorscale = [ [0.0, '#EDC9AF'], [0.25, '#EDC9AF'], [0.25, '#CD853F'], [0.5, '#CD853F'], [0.5, '#808080'], [0.75, '#808080'], [0.75, '#DAA520'], [1.0, '#DAA520'] ] colorbar_labels = ["์‚ฌ๋ง‰(็ ‚)", "์•”์งˆ(ๅท–)", "์•”์„(ๅฒฉ)", "๋ชจ๋ž˜(ๆฒ™)"] else: realistic_colorscale = [ [0.0, '#E6C288'], [0.25, '#E6C288'], [0.25, '#556B2F'], [0.5, '#556B2F'], [0.5, '#808080'], [0.75, '#808080'], [0.75, '#A0522D'], [1.0, '#A0522D'] ] colorbar_labels = ["ํ‡ด์ (ๅœŸ)", "์‹์ƒ(่‰)", "์•”์„(ๅฒฉ)", "ํ‘œํ† (ๅœŸ)"] # ์‹œ๊ฐ์  ๋…ธ์ด์ฆˆ visual_z = (elevation + np.random.normal(0, 0.2, elevation.shape)).round(2) final_surface_color = biome_noisy final_colorscale = realistic_colorscale final_cmin = 0 final_cmax = 3 final_colorbar = dict( title=dict(text="์ง€ํ‘œ ์ƒํƒœ", font=dict(color='white')), tickvals=[0.37, 1.12, 1.87, 2.62], ticktext=colorbar_labels, tickfont=dict(color='white') ) # ํ…์Šค์ฒ˜ ์ด๋ฏธ์ง€ ์ฒ˜๋ฆฌ if texture_path and os.path.exists(texture_path) and Image: try: img = Image.open(texture_path).convert('L') img = img.resize((w, h)) img_array = np.array(img) / 255.0 final_surface_color = img_array final_colorscale = 'Gray' final_cmin = 0 final_cmax = 1 final_colorbar = dict(title="ํ…์Šค์ฒ˜ ๋ช…์•”") except Exception as e: print(f"Texture error: {e}") # 3D Plot lighting_effects = dict(ambient=0.4, diffuse=0.5, roughness=0.9, specular=0.1, fresnel=0.2) trace_terrain = go.Surface( z=visual_z, x=x, y=y, surfacecolor=final_surface_color, colorscale=final_colorscale, cmin=final_cmin, cmax=final_cmax, colorbar=final_colorbar, lighting=lighting_effects, hoverinfo='z' ) data = [trace_terrain] # Water Surface if water_depth_grid is not None: water_mask = water_depth_grid > 0.1 if np.any(water_mask): water_z = elevation + water_depth_grid water_z[~water_mask] = np.nan trace_water = go.Surface( z=water_z, x=x, y=y, colorscale=[[0, 'rgba(30,144,255,0.7)'], [1, 'rgba(30,144,255,0.7)']], showscale=False, lighting=dict(ambient=0.6, diffuse=0.5, specular=0.8, roughness=0.1), hoverinfo='skip' ) data.append(trace_water) elif add_water: water_z = np.ones_like(elevation) * water_level trace_water = go.Surface( z=water_z, x=x, y=y, hoverinfo='none', lighting=dict(ambient=0.6, diffuse=0.6, specular=0.5) ) data.append(trace_water) # Layout fig = go.Figure(data=data) fig.update_layout( title=dict(text=title, font=dict(color='white', size=16)), uirevision='terrain_viz', scene=dict( xaxis=dict(title='X (m)', backgroundcolor='#1a1a2e', gridcolor='#444', color='#cccccc'), yaxis=dict(title='Y (m)', backgroundcolor='#1a1a2e', gridcolor='#444', color='#cccccc'), zaxis=dict(title='Elevation', backgroundcolor='#1a1a2e', gridcolor='#444', color='#cccccc'), bgcolor='#0e1117', camera=dict( eye=dict(x=1.6, y=-1.6, z=0.8), center=dict(x=0, y=0, z=-0.2), up=dict(x=0, y=0, z=1) ) if force_camera else None, aspectmode='manual', aspectratio=dict(x=1, y=1, z=0.35) ), paper_bgcolor='#0e1117', plot_bgcolor='#0e1117', height=700, margin=dict(l=10, r=10, t=50, b=10), ) return fig