Commit ·
90d9dd9
1
Parent(s): 5b29015
Added HF emoji
Browse files- app.py +4 -0
- backend/storage.py +7 -39
- frontend/game_viewer.html +64 -6
app.py
CHANGED
|
@@ -111,6 +111,10 @@ def get_viewer_html(scene_id="welcome"):
|
|
| 111 |
if not scene_data:
|
| 112 |
return '<div style="color: red;">Scene not found</div>'
|
| 113 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 114 |
# Read the viewer HTML template
|
| 115 |
viewer_path = os.path.join(os.path.dirname(__file__), "frontend", "game_viewer.html")
|
| 116 |
with open(viewer_path, 'r') as f:
|
|
|
|
| 111 |
if not scene_data:
|
| 112 |
return '<div style="color: red;">Scene not found</div>'
|
| 113 |
|
| 114 |
+
# Add the static assets base URL for model loading
|
| 115 |
+
# On local dev: http://localhost:8000, on HF Spaces: empty (relative)
|
| 116 |
+
scene_data["static_base_url"] = FASTAPI_INTERNAL if not IS_HF_SPACES else ""
|
| 117 |
+
|
| 118 |
# Read the viewer HTML template
|
| 119 |
viewer_path = os.path.join(os.path.dirname(__file__), "frontend", "game_viewer.html")
|
| 120 |
with open(viewer_path, 'r') as f:
|
backend/storage.py
CHANGED
|
@@ -80,47 +80,15 @@ def initialize_default_scene():
|
|
| 80 |
background_color="#1a0a20", # Dark purple (will be overridden by skybox)
|
| 81 |
)
|
| 82 |
|
| 83 |
-
#
|
| 84 |
objects = [
|
| 85 |
-
# Red cube - classic demo object
|
| 86 |
create_game_object(
|
| 87 |
-
object_type="
|
| 88 |
-
name="
|
| 89 |
-
position=create_vector3(
|
| 90 |
-
scale=create_vector3(2, 2, 2),
|
| 91 |
-
material=create_material(color="#ff4444", metalness=0.3, roughness=0.4),
|
| 92 |
-
),
|
| 93 |
-
# Blue sphere - metallic
|
| 94 |
-
create_game_object(
|
| 95 |
-
object_type="sphere",
|
| 96 |
-
name="BlueSphere",
|
| 97 |
-
position=create_vector3(-4, 1.5, -2),
|
| 98 |
scale=create_vector3(3, 3, 3),
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
# Green cylinder
|
| 102 |
-
create_game_object(
|
| 103 |
-
object_type="cylinder",
|
| 104 |
-
name="GreenCylinder",
|
| 105 |
-
position=create_vector3(0, 1.5, -6),
|
| 106 |
-
scale=create_vector3(1.5, 3, 1.5),
|
| 107 |
-
material=create_material(color="#44ff44", metalness=0.2, roughness=0.6),
|
| 108 |
-
),
|
| 109 |
-
# Yellow torus
|
| 110 |
-
create_game_object(
|
| 111 |
-
object_type="torus",
|
| 112 |
-
name="YellowTorus",
|
| 113 |
-
position=create_vector3(-3, 2, -7),
|
| 114 |
-
scale=create_vector3(2, 2, 2),
|
| 115 |
-
material=create_material(color="#ffcc00", metalness=0.6, roughness=0.3),
|
| 116 |
-
),
|
| 117 |
-
# Purple cone
|
| 118 |
-
create_game_object(
|
| 119 |
-
object_type="cone",
|
| 120 |
-
name="PurpleCone",
|
| 121 |
-
position=create_vector3(5, 1, -5),
|
| 122 |
-
scale=create_vector3(1.5, 3, 1.5),
|
| 123 |
-
material=create_material(color="#aa44ff", metalness=0.4, roughness=0.5),
|
| 124 |
),
|
| 125 |
]
|
| 126 |
|
|
@@ -152,7 +120,7 @@ def initialize_default_scene():
|
|
| 152 |
storage.save(scene)
|
| 153 |
print(f"✓ Initialized Welcome Scene (ID: welcome)")
|
| 154 |
print(f" - 25x25 world with sunset skybox")
|
| 155 |
-
print(f" -
|
| 156 |
print(f" - FPS physics controller ready")
|
| 157 |
|
| 158 |
|
|
|
|
| 80 |
background_color="#1a0a20", # Dark purple (will be overridden by skybox)
|
| 81 |
)
|
| 82 |
|
| 83 |
+
# Hugging Face emoji model at center (animated)
|
| 84 |
objects = [
|
|
|
|
| 85 |
create_game_object(
|
| 86 |
+
object_type="model",
|
| 87 |
+
name="HuggingFace",
|
| 88 |
+
position=create_vector3(0, 1.5, 0),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 89 |
scale=create_vector3(3, 3, 3),
|
| 90 |
+
model_path="/static/models/Norod78/huggingface_emoji.glb",
|
| 91 |
+
metadata={"animate": True, "baseY": 1.5},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 92 |
),
|
| 93 |
]
|
| 94 |
|
|
|
|
| 120 |
storage.save(scene)
|
| 121 |
print(f"✓ Initialized Welcome Scene (ID: welcome)")
|
| 122 |
print(f" - 25x25 world with sunset skybox")
|
| 123 |
+
print(f" - Hugging Face emoji model at center")
|
| 124 |
print(f" - FPS physics controller ready")
|
| 125 |
|
| 126 |
|
frontend/game_viewer.html
CHANGED
|
@@ -75,6 +75,7 @@
|
|
| 75 |
|
| 76 |
// Particle systems
|
| 77 |
let particleSystems = new Map();
|
|
|
|
| 78 |
|
| 79 |
// UI overlay container
|
| 80 |
let uiContainer = null;
|
|
@@ -555,13 +556,13 @@
|
|
| 555 |
canvas.height = size;
|
| 556 |
const ctx = canvas.getContext('2d');
|
| 557 |
|
| 558 |
-
// Background -
|
| 559 |
-
ctx.fillStyle = '#
|
| 560 |
ctx.fillRect(0, 0, size, size);
|
| 561 |
|
| 562 |
-
// Major grid lines
|
| 563 |
const majorSpacing = size / 8;
|
| 564 |
-
ctx.strokeStyle = '#
|
| 565 |
ctx.lineWidth = 2;
|
| 566 |
ctx.beginPath();
|
| 567 |
for (let i = 0; i <= 8; i++) {
|
|
@@ -573,9 +574,9 @@
|
|
| 573 |
}
|
| 574 |
ctx.stroke();
|
| 575 |
|
| 576 |
-
// Minor grid lines (
|
| 577 |
const minorSpacing = majorSpacing / 4;
|
| 578 |
-
ctx.strokeStyle = '
|
| 579 |
ctx.lineWidth = 1;
|
| 580 |
ctx.beginPath();
|
| 581 |
for (let i = 0; i <= 32; i++) {
|
|
@@ -831,6 +832,47 @@
|
|
| 831 |
case 'torus':
|
| 832 |
geometry = new THREE.TorusGeometry(obj.scale.x, obj.scale.x * 0.4, 16, 100);
|
| 833 |
break;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 834 |
default:
|
| 835 |
console.warn('Unknown object type:', obj.type);
|
| 836 |
return;
|
|
@@ -1055,6 +1097,9 @@
|
|
| 1055 |
// Update particle systems
|
| 1056 |
updateParticleSystems(delta);
|
| 1057 |
|
|
|
|
|
|
|
|
|
|
| 1058 |
// Render using composer (for outlines) instead of direct renderer
|
| 1059 |
if (composer) {
|
| 1060 |
composer.render();
|
|
@@ -1942,6 +1987,19 @@
|
|
| 1942 |
}
|
| 1943 |
}
|
| 1944 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1945 |
function updateParticleSystems(delta) {
|
| 1946 |
particleSystems.forEach((system, id) => {
|
| 1947 |
const positions = system.geometry.attributes.position.array;
|
|
|
|
| 75 |
|
| 76 |
// Particle systems
|
| 77 |
let particleSystems = new Map();
|
| 78 |
+
let animatedModels = []; // Models with animation metadata
|
| 79 |
|
| 80 |
// UI overlay container
|
| 81 |
let uiContainer = null;
|
|
|
|
| 556 |
canvas.height = size;
|
| 557 |
const ctx = canvas.getContext('2d');
|
| 558 |
|
| 559 |
+
// Background - bright blue
|
| 560 |
+
ctx.fillStyle = '#2080dd';
|
| 561 |
ctx.fillRect(0, 0, size, size);
|
| 562 |
|
| 563 |
+
// Major grid lines - white
|
| 564 |
const majorSpacing = size / 8;
|
| 565 |
+
ctx.strokeStyle = '#ffffff';
|
| 566 |
ctx.lineWidth = 2;
|
| 567 |
ctx.beginPath();
|
| 568 |
for (let i = 0; i <= 8; i++) {
|
|
|
|
| 574 |
}
|
| 575 |
ctx.stroke();
|
| 576 |
|
| 577 |
+
// Minor grid lines - white (semi-transparent)
|
| 578 |
const minorSpacing = majorSpacing / 4;
|
| 579 |
+
ctx.strokeStyle = 'rgba(255, 255, 255, 0.5)';
|
| 580 |
ctx.lineWidth = 1;
|
| 581 |
ctx.beginPath();
|
| 582 |
for (let i = 0; i <= 32; i++) {
|
|
|
|
| 832 |
case 'torus':
|
| 833 |
geometry = new THREE.TorusGeometry(obj.scale.x, obj.scale.x * 0.4, 16, 100);
|
| 834 |
break;
|
| 835 |
+
case 'model':
|
| 836 |
+
// Load GLB/GLTF model asynchronously
|
| 837 |
+
if (obj.model_path) {
|
| 838 |
+
const loader = new GLTFLoader();
|
| 839 |
+
// Use static_base_url from scene data for correct server
|
| 840 |
+
const staticBase = sceneData.static_base_url || '';
|
| 841 |
+
const modelUrl = staticBase + obj.model_path;
|
| 842 |
+
loader.load(
|
| 843 |
+
modelUrl,
|
| 844 |
+
(gltf) => {
|
| 845 |
+
const model = gltf.scene;
|
| 846 |
+
model.position.set(obj.position.x, obj.position.y, obj.position.z);
|
| 847 |
+
model.scale.set(obj.scale.x, obj.scale.y, obj.scale.z);
|
| 848 |
+
model.rotation.set(
|
| 849 |
+
THREE.MathUtils.degToRad(obj.rotation.x),
|
| 850 |
+
THREE.MathUtils.degToRad(obj.rotation.y),
|
| 851 |
+
THREE.MathUtils.degToRad(obj.rotation.z)
|
| 852 |
+
);
|
| 853 |
+
model.userData = {
|
| 854 |
+
id: obj.id,
|
| 855 |
+
name: obj.name,
|
| 856 |
+
type: 'model',
|
| 857 |
+
isSceneObject: true,
|
| 858 |
+
animate: obj.metadata?.animate || false,
|
| 859 |
+
baseY: obj.metadata?.baseY || obj.position.y,
|
| 860 |
+
};
|
| 861 |
+
scene.add(model);
|
| 862 |
+
|
| 863 |
+
// Track animated models
|
| 864 |
+
if (obj.metadata?.animate) {
|
| 865 |
+
animatedModels.push(model);
|
| 866 |
+
console.log(`Loaded animated model: ${obj.name} from ${modelUrl}`);
|
| 867 |
+
} else {
|
| 868 |
+
console.log(`Loaded model: ${obj.name} from ${modelUrl}`);
|
| 869 |
+
}
|
| 870 |
+
},
|
| 871 |
+
undefined,
|
| 872 |
+
(error) => console.error(`Failed to load model ${modelUrl}:`, error)
|
| 873 |
+
);
|
| 874 |
+
}
|
| 875 |
+
return; // Skip the rest of the geometry/material creation
|
| 876 |
default:
|
| 877 |
console.warn('Unknown object type:', obj.type);
|
| 878 |
return;
|
|
|
|
| 1097 |
// Update particle systems
|
| 1098 |
updateParticleSystems(delta);
|
| 1099 |
|
| 1100 |
+
// Update animated models (rotate + bob up/down)
|
| 1101 |
+
updateAnimatedModels(time);
|
| 1102 |
+
|
| 1103 |
// Render using composer (for outlines) instead of direct renderer
|
| 1104 |
if (composer) {
|
| 1105 |
composer.render();
|
|
|
|
| 1987 |
}
|
| 1988 |
}
|
| 1989 |
|
| 1990 |
+
function updateAnimatedModels(time) {
|
| 1991 |
+
// Animate models with rotate + sine wave bobbing
|
| 1992 |
+
const timeInSeconds = time / 1000;
|
| 1993 |
+
animatedModels.forEach(model => {
|
| 1994 |
+
// Rotate slowly around Y axis
|
| 1995 |
+
model.rotation.y = timeInSeconds * 0.5;
|
| 1996 |
+
|
| 1997 |
+
// Bob up and down with sine wave
|
| 1998 |
+
const baseY = model.userData.baseY || 1.5;
|
| 1999 |
+
model.position.y = baseY + Math.sin(timeInSeconds * 2) * 0.3;
|
| 2000 |
+
});
|
| 2001 |
+
}
|
| 2002 |
+
|
| 2003 |
function updateParticleSystems(delta) {
|
| 2004 |
particleSystems.forEach((system, id) => {
|
| 2005 |
const positions = system.geometry.attributes.position.array;
|