Spaces:
Paused
Paused
| import gradio as gr | |
| import spaces | |
| import numpy as np | |
| import noise | |
| import random | |
| import json | |
| import uuid | |
| import os | |
| import tempfile | |
| from fastapi import FastAPI | |
| from fastapi.responses import JSONResponse | |
| # ---------- Генератор мира ---------- | |
| def generate_world(seed: int, size: int): | |
| np.random.seed(seed) | |
| random.seed(seed) | |
| scale = 30.0 | |
| octaves = 6 | |
| persistence = 0.5 | |
| lacunarity = 2.0 | |
| temp_noise = np.zeros((size, size)) | |
| moist_noise = np.zeros((size, size)) | |
| for x in range(size): | |
| for z in range(size): | |
| temp_noise[x][z] = noise.pnoise2(x/scale/2+seed, z/scale/2+seed, octaves=4) | |
| moist_noise[x][z] = noise.pnoise2(x/scale/2+seed*2, z/scale/2+seed*2, octaves=4) | |
| temp_noise = (temp_noise - temp_noise.min()) / (temp_noise.max() - temp_noise.min() + 1e-8) | |
| moist_noise = (moist_noise - moist_noise.min()) / (moist_noise.max() - moist_noise.min() + 1e-8) | |
| world_blocks = [] | |
| for x in range(size): | |
| for z in range(size): | |
| h = noise.pnoise2(x/scale+seed, z/scale+seed, octaves=octaves, | |
| persistence=persistence, lacunarity=lacunarity) | |
| c = noise.pnoise2(x/scale*2+seed*3, z/scale*2+seed*3, octaves=4) | |
| h = h * 0.7 + c * 0.3 | |
| height_norm = (h + 1) / 2.0 | |
| t = temp_noise[x][z] | |
| m = moist_noise[x][z] | |
| water_level = 0.45 | |
| mountain_threshold = 0.75 | |
| y_max = int(10 + height_norm * 15) | |
| for y in range(0, y_max + 1): | |
| if y == 0: | |
| block = "bedrock" | |
| elif y <= 3: | |
| block = "stone" | |
| elif y <= y_max - 4: | |
| block = "dirt" | |
| else: | |
| if height_norm < water_level: | |
| block = "water" if y_max <= 12 else "sand" | |
| elif height_norm > mountain_threshold: | |
| block = "stone" if y == y_max else "dirt" | |
| else: | |
| if t < 0.3 and m < 0.3: | |
| block = "sand" if y == y_max else "sandstone" | |
| elif t < 0.3 and m > 0.6: | |
| block = "grass" if y == y_max else "dirt" | |
| elif t > 0.7 and m > 0.5: | |
| block = "grass" if y == y_max else "dirt" | |
| if y == y_max and random.random() < 0.1: | |
| _make_tree(world_blocks, x, y, z) | |
| else: | |
| block = "grass" if y == y_max else "dirt" | |
| if block != "bedrock": | |
| world_blocks.append({"x": x, "y": y, "z": z, "block": block}) | |
| world_blocks.sort(key=lambda b: (b["y"], b["x"], b["z"])) | |
| return world_blocks | |
| def _make_tree(blocks_list, x, y, z, trunk_height=3): | |
| for dy in range(1, trunk_height+1): | |
| blocks_list.append({"x": x, "y": y+dy, "z": z, "block": "wood"}) | |
| for dx in (-1, 0, 1): | |
| for dz in (-1, 0, 1): | |
| for dy in range(trunk_height, trunk_height+2): | |
| if abs(dx) == 1 and abs(dz) == 1: | |
| continue | |
| blocks_list.append({"x": x+dx, "y": y+dy, "z": z+dz, "block": "leaves"}) | |
| def chunk_world(blocks): | |
| full_json = json.dumps(blocks, ensure_ascii=False) | |
| max_len = 20000 | |
| if len(full_json) <= max_len: | |
| return [full_json] | |
| parts = [] | |
| start = 0 | |
| while start < len(blocks): | |
| chunk_blocks = [] | |
| current_len = 1 | |
| idx = start | |
| while idx < len(blocks): | |
| block_str = json.dumps(blocks[idx], ensure_ascii=False) | |
| extra = 0 if not chunk_blocks else 1 | |
| if current_len + len(block_str) + extra + 1 > max_len: | |
| break | |
| chunk_blocks.append(blocks[idx]) | |
| current_len += len(block_str) + (1 if chunk_blocks else 0) | |
| idx += 1 | |
| if not chunk_blocks: | |
| raise RuntimeError("Block too large for chunk") | |
| parts.append(json.dumps(chunk_blocks, ensure_ascii=False)) | |
| start = idx | |
| return parts | |
| # ---------- GPU только для генерации ---------- | |
| def generate(seed: int, size: int): | |
| world = generate_world(seed, size) | |
| chunks = chunk_world(world) | |
| session_id = str(uuid.uuid4()) | |
| filepath = os.path.join(tempfile.gettempdir(), f"mc_session_{session_id}.json") | |
| with open(filepath, "w", encoding="utf-8") as f: | |
| json.dump({"chunks": chunks, "total_parts": len(chunks), "seed": seed, "size": size}, f) | |
| return {"session_id": session_id, "total_parts": len(chunks)} | |
| # ---------- Функция получения части (CPU, без очереди) ---------- | |
| def get_part(session_id: str, part_num: int): | |
| part_num = int(part_num) | |
| filepath = os.path.join( | |
| tempfile.gettempdir(), | |
| f"mc_session_{session_id}.json" | |
| ) | |
| if not os.path.exists(filepath): | |
| return { | |
| "error": "Session not found" | |
| } | |
| with open(filepath, "r", encoding="utf-8") as f: | |
| data = json.load(f) | |
| if part_num < 0 or part_num >= data["total_parts"]: | |
| return { | |
| "error": "Invalid part number", | |
| "max": data["total_parts"] | |
| } | |
| return json.loads( | |
| data["chunks"][part_num] | |
| ) | |
| # ---------- Gradio UI ---------- | |
| with gr.Blocks() as demo: | |
| gr.Markdown( | |
| "# 🟫 Minecraft World Generator (ZeroGPU)" | |
| ) | |
| with gr.Tab("Generate"): | |
| seed_in = gr.Number( | |
| value=42, | |
| label="Seed", | |
| precision=0 | |
| ) | |
| size_in = gr.Slider( | |
| minimum=20, | |
| maximum=50, | |
| value=35, | |
| step=5, | |
| label="Size" | |
| ) | |
| gen_btn = gr.Button( | |
| "Generate World" | |
| ) | |
| gen_out = gr.JSON( | |
| label="Result" | |
| ) | |
| with gr.Tab("Get Part"): | |
| sess_in = gr.Textbox( | |
| label="Session ID" | |
| ) | |
| part_in = gr.Number( | |
| value=0, | |
| label="Part Number", | |
| precision=0 | |
| ) | |
| get_btn = gr.Button( | |
| "Get Part" | |
| ) | |
| part_out = gr.JSON( | |
| label="Chunk" | |
| ) | |
| # GPU генерация | |
| gen_btn.click( | |
| fn=generate, | |
| inputs=[ | |
| seed_in, | |
| size_in | |
| ], | |
| outputs=gen_out | |
| ) | |
| # CPU получение блока | |
| get_btn.click( | |
| fn=get_part, | |
| inputs=[ | |
| sess_in, | |
| part_in | |
| ], | |
| outputs=part_out | |
| ) | |
| app = demo.app | |
| def api_get_part(session_id: str, part_num: int): | |
| return JSONResponse( | |
| content=get_part(session_id, part_num) | |
| ) | |
| demo.launch() |