Spaces:
Running
Running
import streamlit as st | |
import os | |
import random | |
def scan_assets(): | |
"""Discover textures, bump maps, glTF models, and OBJ(+MTL) pairs.""" | |
files = [f for f in os.listdir() if os.path.isfile(f)] | |
img_exts = (".jpg", ".jpeg", ".png", ".gif") | |
# Textures (exclude bump/normal) | |
textures = [ | |
f for f in files | |
if f.lower().endswith(img_exts) | |
and not any(tag in f.lower() for tag in ("bump", "normal")) | |
] | |
# Bump/NORMAL map (take the first one, if any) | |
bump_maps = [ | |
f for f in files | |
if f.lower().endswith(img_exts) | |
and any(tag in f.lower() for tag in ("bump", "normal")) | |
] | |
# glTF models | |
gltf_models = [f for f in files if f.lower().endswith((".glb", ".gltf"))] | |
# OBJ models + their MTL partners | |
obj_models = [f for f in files if f.lower().endswith(".obj")] | |
mtl_files = { | |
os.path.splitext(f)[0]: f | |
for f in files | |
if f.lower().endswith(".mtl") | |
} | |
models = [] | |
idx = 0 | |
# Register glTF entries | |
for gltf in gltf_models: | |
models.append({ | |
"type": "gltf", | |
"asset_id": f"model{idx}", | |
"src": gltf | |
}) | |
idx += 1 | |
# Register OBJ entries | |
for obj in obj_models: | |
base = os.path.splitext(obj)[0] | |
mtl = mtl_files.get(base) | |
entry = { | |
"type": "obj", | |
"obj_id": f"model{idx}-obj", | |
"obj": obj, | |
"mtl_id": f"model{idx}-mtl" if mtl else None, | |
"mtl": mtl | |
} | |
models.append(entry) | |
idx += 1 | |
return textures, bump_maps, models | |
def main(): | |
st.title("🔳 A-Frame Tilemap with Mixed 3D Models") | |
grid_size = st.sidebar.slider("Grid Size", 1, 20, 10) | |
textures, bump_maps, models = scan_assets() | |
if not textures or not models: | |
st.warning("⚠️ Drop at least one .jpg/.png and one .glb/.obj (with optional .mtl) in this folder.") | |
return | |
# --- Build <a-assets> --- | |
asset_tags = [] | |
for i, tex in enumerate(textures): | |
asset_tags.append(f'<img id="tex{i}" src="{tex}">') | |
if bump_maps: | |
asset_tags.append(f'<img id="bump0" src="{bump_maps[0]}">') | |
for m in models: | |
if m["type"] == "gltf": | |
asset_tags.append( | |
f'<a-asset-item id="{m["asset_id"]}" src="{m["src"]}"></a-asset-item>' | |
) | |
else: | |
asset_tags.append( | |
f'<a-asset-item id="{m["obj_id"]}" src="{m["obj"]}"></a-asset-item>' | |
) | |
if m["mtl_id"]: | |
asset_tags.append( | |
f'<a-asset-item id="{m["mtl_id"]}" src="{m["mtl"]}"></a-asset-item>' | |
) | |
assets_html = "\n ".join(asset_tags) | |
# JS arrays for textures & models | |
tex_js = ", ".join(f'"#tex{i}"' for i in range(len(textures))) | |
models_js_elems = [] | |
for m in models: | |
if m["type"] == "gltf": | |
models_js_elems.append(f'{{type:"gltf", id:"#{m["asset_id"]}"}}') | |
else: | |
if m["mtl_id"]: | |
models_js_elems.append( | |
f'{{type:"obj", obj:"#{m["obj_id"]}", mtl:"#{m["mtl_id"]}"}}' | |
) | |
else: | |
models_js_elems.append( | |
f'{{type:"obj", obj:"#{m["obj_id"]}"}}' | |
) | |
models_js = ", ".join(models_js_elems) | |
# Ground material (with optional bump) | |
if bump_maps: | |
ground_mat = "ground.setAttribute('material','color:#228B22; bumpMap:#bump0; bumpScale:0.2');" | |
else: | |
ground_mat = "ground.setAttribute('material','color:#228B22');" | |
# --- Final HTML --- | |
html = f""" | |
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="utf-8"> | |
<title>Tilemap Scene</title> | |
<script src="https://aframe.io/releases/1.2.0/aframe.min.js"></script> | |
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/aframe-extras.loaders.min.js"></script> | |
</head> | |
<body> | |
<a-scene> | |
<a-assets> | |
{assets_html} | |
</a-assets> | |
<!-- Lights --> | |
<a-entity light="type: ambient; color: #BBB"></a-entity> | |
<a-entity light="type: directional; color: #FFF; intensity:0.6" position="1 1 0"></a-entity> | |
<a-entity light="type: point; intensity:0.6" position="0 5 0"></a-entity> | |
<!-- Camera --> | |
<a-entity camera look-controls position="0 {grid_size} {grid_size}"></a-entity> | |
<!-- Tiles & Models --> | |
<a-entity id="tilemap"></a-entity> | |
</a-scene> | |
<script> | |
document.addEventListener('DOMContentLoaded', function() {{ | |
var scene = document.querySelector('a-scene'); | |
var tilemap = document.querySelector('#tilemap'); | |
var textures = [{tex_js}]; | |
var models = [{models_js}]; | |
var grid = {grid_size}; | |
for (var i = 0; i < grid; i++) {{ | |
for (var j = 0; j < grid; j++) {{ | |
var x = i - grid/2; | |
var z = j - grid/2; | |
// Base tile | |
var tile = document.createElement('a-box'); | |
tile.setAttribute('width', 1); | |
tile.setAttribute('height', 0.1); | |
tile.setAttribute('depth', 1); | |
var tidx = Math.floor(Math.random() * textures.length); | |
tile.setAttribute('material', 'src:' + textures[tidx] + '; repeat:1 1'); | |
tile.setAttribute('position', x + ' 0 ' + z); | |
tilemap.appendChild(tile); | |
// Random model | |
var m = models[Math.floor(Math.random() * models.length)]; | |
var ent = document.createElement('a-entity'); | |
if (m.type === 'gltf') {{ | |
ent.setAttribute('gltf-model', m.id); | |
}} else {{ | |
var cmd = 'obj: ' + m.obj; | |
if (m.mtl) cmd += '; mtl: ' + m.mtl; | |
ent.setAttribute('obj-model', cmd); | |
}} | |
ent.setAttribute('scale', '0.5 0.5 0.5'); | |
ent.setAttribute('position', x + ' 0.5 ' + z); | |
tilemap.appendChild(ent); | |
}} | |
}} | |
// Ground plane | |
var ground = document.createElement('a-plane'); | |
ground.setAttribute('width', grid * 2); | |
ground.setAttribute('height', grid * 2); | |
ground.setAttribute('rotation', '-90 0 0'); | |
{ground_mat} | |
ground.setAttribute('position', '0 -0.05 0'); | |
scene.insertBefore(ground, scene.firstChild); | |
}}); | |
</script> | |
</body> | |
</html> | |
""" | |
st.components.v1.html(html, height=600, scrolling=False) | |
if __name__ == "__main__": | |
main() | |