Update app.py
Browse files
app.py
CHANGED
@@ -0,0 +1,284 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import gradio as gr
|
2 |
+
import json
|
3 |
+
import os
|
4 |
+
from pathlib import Path
|
5 |
+
import shutil
|
6 |
+
|
7 |
+
def create_game_html(model_path):
|
8 |
+
html_template = f"""
|
9 |
+
<!DOCTYPE html>
|
10 |
+
<html>
|
11 |
+
<head>
|
12 |
+
<title>3D Open World Game</title>
|
13 |
+
<style>
|
14 |
+
body {{ margin: 0; }}
|
15 |
+
canvas {{ display: block; }}
|
16 |
+
#instructions {{
|
17 |
+
position: absolute;
|
18 |
+
top: 10px;
|
19 |
+
left: 10px;
|
20 |
+
background: rgba(0,0,0,0.7);
|
21 |
+
color: white;
|
22 |
+
padding: 10px;
|
23 |
+
border-radius: 5px;
|
24 |
+
font-family: Arial, sans-serif;
|
25 |
+
}}
|
26 |
+
</style>
|
27 |
+
<script async src="https://unpkg.com/[email protected]/dist/es-module-shims.js"></script>
|
28 |
+
<script type="importmap">
|
29 |
+
{{
|
30 |
+
"imports": {{
|
31 |
+
"three": "https://unpkg.com/[email protected]/build/three.module.js",
|
32 |
+
"three/addons/": "https://unpkg.com/[email protected]/examples/jsm/"
|
33 |
+
}}
|
34 |
+
}}
|
35 |
+
</script>
|
36 |
+
</head>
|
37 |
+
<body>
|
38 |
+
<div id="instructions">
|
39 |
+
Controls:<br>
|
40 |
+
W - Move Forward<br>
|
41 |
+
S - Move Backward<br>
|
42 |
+
A - Move Left<br>
|
43 |
+
D - Move Right<br>
|
44 |
+
Mouse - Look Around
|
45 |
+
</div>
|
46 |
+
<script type="module">
|
47 |
+
import * as THREE from 'three';
|
48 |
+
import {{ GLTFLoader }} from 'three/addons/loaders/GLTFLoader.js';
|
49 |
+
import {{ PointerLockControls }} from 'three/addons/controls/PointerLockControls.js';
|
50 |
+
|
51 |
+
// ๊ธฐ๋ณธ ์ค์
|
52 |
+
const scene = new THREE.Scene();
|
53 |
+
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
|
54 |
+
const renderer = new THREE.WebGLRenderer();
|
55 |
+
renderer.setSize(window.innerWidth, window.innerHeight);
|
56 |
+
renderer.shadowMap.enabled = true;
|
57 |
+
document.body.appendChild(renderer.domElement);
|
58 |
+
|
59 |
+
// ํฌ์ธํฐ ๋ฝ ์ปจํธ๋กค
|
60 |
+
const controls = new PointerLockControls(camera, document.body);
|
61 |
+
|
62 |
+
// ํค๋ณด๋ ์
๋ ฅ ์ฒ๋ฆฌ
|
63 |
+
const moveState = {{
|
64 |
+
forward: false,
|
65 |
+
backward: false,
|
66 |
+
left: false,
|
67 |
+
right: false
|
68 |
+
}};
|
69 |
+
|
70 |
+
document.addEventListener('keydown', (event) => {{
|
71 |
+
switch (event.code) {{
|
72 |
+
case 'KeyW':
|
73 |
+
moveState.forward = true;
|
74 |
+
break;
|
75 |
+
case 'KeyS':
|
76 |
+
moveState.backward = true;
|
77 |
+
break;
|
78 |
+
case 'KeyA':
|
79 |
+
moveState.left = true;
|
80 |
+
break;
|
81 |
+
case 'KeyD':
|
82 |
+
moveState.right = true;
|
83 |
+
break;
|
84 |
+
}}
|
85 |
+
}});
|
86 |
+
|
87 |
+
document.addEventListener('keyup', (event) => {{
|
88 |
+
switch (event.code) {{
|
89 |
+
case 'KeyW':
|
90 |
+
moveState.forward = false;
|
91 |
+
break;
|
92 |
+
case 'KeyS':
|
93 |
+
moveState.backward = false;
|
94 |
+
break;
|
95 |
+
case 'KeyA':
|
96 |
+
moveState.left = false;
|
97 |
+
break;
|
98 |
+
case 'KeyD':
|
99 |
+
moveState.right = false;
|
100 |
+
break;
|
101 |
+
}}
|
102 |
+
}});
|
103 |
+
|
104 |
+
// ํด๋ฆญ์ผ๋ก ๊ฒ์ ์์
|
105 |
+
document.addEventListener('click', () => {{
|
106 |
+
controls.lock();
|
107 |
+
}});
|
108 |
+
|
109 |
+
// ์งํ ์์ฑ
|
110 |
+
const terrainGeometry = new THREE.PlaneGeometry(1000, 1000, 100, 100);
|
111 |
+
const terrainMaterial = new THREE.MeshStandardMaterial({{
|
112 |
+
color: 0x3a8c3a,
|
113 |
+
wireframe: false
|
114 |
+
}});
|
115 |
+
const terrain = new THREE.Mesh(terrainGeometry, terrainMaterial);
|
116 |
+
terrain.rotation.x = -Math.PI / 2;
|
117 |
+
terrain.receiveShadow = true;
|
118 |
+
scene.add(terrain);
|
119 |
+
|
120 |
+
// ์งํ ๋๋ฎ์ด ์ค์
|
121 |
+
const vertices = terrainGeometry.attributes.position.array;
|
122 |
+
for (let i = 0; i < vertices.length; i += 3) {{
|
123 |
+
vertices[i + 2] = Math.random() * 10;
|
124 |
+
}}
|
125 |
+
terrainGeometry.attributes.position.needsUpdate = true;
|
126 |
+
terrainGeometry.computeVertexNormals();
|
127 |
+
|
128 |
+
// ์กฐ๋ช
์ค์
|
129 |
+
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
|
130 |
+
scene.add(ambientLight);
|
131 |
+
|
132 |
+
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
|
133 |
+
directionalLight.position.set(100, 100, 100);
|
134 |
+
directionalLight.castShadow = true;
|
135 |
+
scene.add(directionalLight);
|
136 |
+
|
137 |
+
// ์บ๋ฆญํฐ ๋ชจ๋ธ ๋ก๋
|
138 |
+
let character;
|
139 |
+
const loader = new GLTFLoader();
|
140 |
+
loader.load('{model_path}', (gltf) => {{
|
141 |
+
character = gltf.scene;
|
142 |
+
character.scale.set(0.5, 0.5, 0.5);
|
143 |
+
character.position.y = 2;
|
144 |
+
character.castShadow = true;
|
145 |
+
character.receiveShadow = true;
|
146 |
+
scene.add(character);
|
147 |
+
|
148 |
+
// ์บ๋ฆญํฐ ๊ทธ๋ฆผ์ ์ค์
|
149 |
+
character.traverse((node) => {{
|
150 |
+
if (node.isMesh) {{
|
151 |
+
node.castShadow = true;
|
152 |
+
node.receiveShadow = true;
|
153 |
+
}}
|
154 |
+
}});
|
155 |
+
}});
|
156 |
+
|
157 |
+
// ์นด๋ฉ๋ผ ์ด๊ธฐ ์์น ์ค์
|
158 |
+
camera.position.set(0, 5, 10);
|
159 |
+
|
160 |
+
// ๋ฐฐ๊ฒฝ ํ๋ ์ค์
|
161 |
+
const skyGeometry = new THREE.SphereGeometry(500, 32, 32);
|
162 |
+
const skyMaterial = new THREE.MeshBasicMaterial({{
|
163 |
+
color: 0x87ceeb,
|
164 |
+
side: THREE.BackSide
|
165 |
+
}});
|
166 |
+
const sky = new THREE.Mesh(skyGeometry, skyMaterial);
|
167 |
+
scene.add(sky);
|
168 |
+
|
169 |
+
// ๋๋ฌด์ ๋ฐ์ ์ถ๊ฐ
|
170 |
+
function addEnvironmentObject(geometry, material, count, yOffset) {{
|
171 |
+
for (let i = 0; i < count; i++) {{
|
172 |
+
const mesh = new THREE.Mesh(geometry, material);
|
173 |
+
const x = (Math.random() - 0.5) * 900;
|
174 |
+
const z = (Math.random() - 0.5) * 900;
|
175 |
+
const y = yOffset;
|
176 |
+
mesh.position.set(x, y, z);
|
177 |
+
mesh.castShadow = true;
|
178 |
+
mesh.receiveShadow = true;
|
179 |
+
scene.add(mesh);
|
180 |
+
}}
|
181 |
+
}}
|
182 |
+
|
183 |
+
// ๋๋ฌด ์์ฑ
|
184 |
+
const treeGeometry = new THREE.ConeGeometry(2, 8, 8);
|
185 |
+
const treeMaterial = new THREE.MeshStandardMaterial({{ color: 0x0d5c0d }});
|
186 |
+
addEnvironmentObject(treeGeometry, treeMaterial, 100, 4);
|
187 |
+
|
188 |
+
// ๋ฐ์ ์์ฑ
|
189 |
+
const rockGeometry = new THREE.DodecahedronGeometry(2);
|
190 |
+
const rockMaterial = new THREE.MeshStandardMaterial({{ color: 0x666666 }});
|
191 |
+
addEnvironmentObject(rockGeometry, rockMaterial, 50, 1);
|
192 |
+
|
193 |
+
// ์ด๋ ์๋
|
194 |
+
const MOVE_SPEED = 0.5;
|
195 |
+
|
196 |
+
// ์ ๋๋ฉ์ด์
๋ฃจํ
|
197 |
+
function animate() {{
|
198 |
+
requestAnimationFrame(animate);
|
199 |
+
|
200 |
+
// ํค๋ณด๋ ์
๋ ฅ์ ๋ฐ๋ฅธ ์ด๋
|
201 |
+
const direction = new THREE.Vector3();
|
202 |
+
const rotation = camera.getWorldDirection(new THREE.Vector3());
|
203 |
+
|
204 |
+
if (moveState.forward) {{
|
205 |
+
direction.add(rotation);
|
206 |
+
}}
|
207 |
+
if (moveState.backward) {{
|
208 |
+
direction.sub(rotation);
|
209 |
+
}}
|
210 |
+
if (moveState.left) {{
|
211 |
+
direction.cross(camera.up).negate();
|
212 |
+
}}
|
213 |
+
if (moveState.right) {{
|
214 |
+
direction.cross(camera.up);
|
215 |
+
}}
|
216 |
+
|
217 |
+
direction.y = 0;
|
218 |
+
direction.normalize();
|
219 |
+
|
220 |
+
if (controls.isLocked) {{
|
221 |
+
controls.moveRight(-direction.z * MOVE_SPEED);
|
222 |
+
controls.moveForward(direction.x * MOVE_SPEED);
|
223 |
+
|
224 |
+
// ์บ๋ฆญํฐ ์์น ์
๋ฐ์ดํธ
|
225 |
+
if (character) {{
|
226 |
+
character.position.x = camera.position.x;
|
227 |
+
character.position.z = camera.position.z;
|
228 |
+
|
229 |
+
// ์ด๋ ๋ฐฉํฅ์ผ๋ก ์บ๋ฆญํฐ ํ์
|
230 |
+
if (direction.length() > 0) {{
|
231 |
+
const angle = Math.atan2(direction.x, direction.z);
|
232 |
+
character.rotation.y = angle;
|
233 |
+
}}
|
234 |
+
}}
|
235 |
+
}}
|
236 |
+
|
237 |
+
renderer.render(scene, camera);
|
238 |
+
}}
|
239 |
+
animate();
|
240 |
+
|
241 |
+
// ์๋์ฐ ๋ฆฌ์ฌ์ด์ฆ ์ฒ๋ฆฌ
|
242 |
+
window.addEventListener('resize', () => {{
|
243 |
+
camera.aspect = window.innerWidth / window.innerHeight;
|
244 |
+
camera.updateProjectionMatrix();
|
245 |
+
renderer.setSize(window.innerWidth, window.innerHeight);
|
246 |
+
}});
|
247 |
+
</script>
|
248 |
+
</body>
|
249 |
+
</html>
|
250 |
+
"""
|
251 |
+
return html_template
|
252 |
+
|
253 |
+
def process_upload(glb_file):
|
254 |
+
# ์
๋ก๋๋ ํ์ผ ์ฒ๋ฆฌ
|
255 |
+
upload_dir = "uploads"
|
256 |
+
os.makedirs(upload_dir, exist_ok=True)
|
257 |
+
|
258 |
+
file_path = Path(glb_file.name)
|
259 |
+
dest_path = Path(upload_dir) / file_path.name
|
260 |
+
shutil.copy(file_path, dest_path)
|
261 |
+
|
262 |
+
# ๊ฒ์ HTML ์์ฑ
|
263 |
+
game_html = create_game_html(str(dest_path))
|
264 |
+
|
265 |
+
# HTML ํ์ผ ์ ์ฅ
|
266 |
+
game_path = Path("game.html")
|
267 |
+
with open(game_path, "w", encoding="utf-8") as f:
|
268 |
+
f.write(game_html)
|
269 |
+
|
270 |
+
return str(game_path)
|
271 |
+
|
272 |
+
# Gradio ์ธํฐํ์ด์ค ์ค์
|
273 |
+
iface = gr.Interface(
|
274 |
+
fn=process_upload,
|
275 |
+
inputs=[
|
276 |
+
gr.File(label="Upload Character Model (GLB)", type="file")
|
277 |
+
],
|
278 |
+
outputs=gr.HTML(label="3D Open World Game"),
|
279 |
+
title="3D Open World Game Generator",
|
280 |
+
description="Upload your character model (GLB) to create a 3D open world game with WASD controls!"
|
281 |
+
)
|
282 |
+
|
283 |
+
if __name__ == "__main__":
|
284 |
+
iface.launch()
|