awacke1 commited on
Commit
16e6b88
·
verified ·
1 Parent(s): c8b3c4e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +141 -95
app.py CHANGED
@@ -9,56 +9,99 @@ def load_aframe_and_extras():
9
  <script src="https://unpkg.com/[email protected]/dist/aframe-event-set-component.min.js"></script>
10
  <script>
11
  let score = 0;
12
- /* … all your original draggable, bouncing, moving-light, moveCamera, fireRaycast … */
13
- // —— Key remap: W→down, S→reset, X→up ——
14
- document.addEventListener('keydown', function(event){
15
- switch(event.key.toLowerCase()) {
16
- case 'w': moveCamera('down'); break;
17
- case 's': moveCamera('reset'); break;
18
- case 'x': moveCamera('up'); break;
19
- case 'q': moveCamera('rotateLeft'); break;
20
- case 'e': moveCamera('rotateRight'); break;
21
- case 'z': moveCamera('reset'); break;
22
- case 'c': moveCamera('ground'); break;
23
- case ' ': fireRaycast(); break;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
  }
25
  });
26
  </script>
27
  """
28
 
29
  @st.cache_data
30
- def encode_file(file_path):
31
- with open(file_path, "rb") as f:
32
- return base64.b64encode(f.read()).decode()
33
-
34
- def create_aframe_entity(stem, file_type, position):
35
- anim = 'animation="property: rotation; to: 0 360 0; loop: true; dur: 20000; easing: linear"'
36
- if file_type == 'obj':
37
- return (f'<a-entity obj-model="obj: #{stem}" '
38
- f'position="{position}" rotation="0 0 0" scale="1 1 1" '
39
- f'class="raycastable" draggable {anim}></a-entity>')
40
- if file_type in ('glb','gltf'):
41
  return (f'<a-entity gltf-model="#{stem}" '
42
- f'position="{position}" rotation="0 0 0" scale="1 1 1" '
43
- f'class="raycastable" draggable {anim}></a-entity>')
44
- return ""
45
 
46
  @st.cache_data
47
- def generate_tilemap(files, directory, gw, gh):
48
  img_exts = ['webp','png']
49
  model_exts = ['obj','glb','gltf']
50
  vid_exts = ['mp4']
51
 
52
- img_files = [f for f in files if f.split('.')[-1] in img_exts]
53
- model_files = [f for f in files if f.split('.')[-1] in model_exts]
54
- vid_files = [f for f in files if f.split('.')[-1] in vid_exts]
55
 
56
- # --- Build assets ---
57
  assets = "<a-assets>"
58
  for f in files:
59
- stem = Path(f).stem
60
- ext = f.split('.')[-1]
61
- data = encode_file(os.path.join(directory, f))
62
  if ext in model_exts:
63
  assets += (f'<a-asset-item id="{stem}" '
64
  f'src="data:application/octet-stream;base64,{data}"></a-asset-item>')
@@ -67,102 +110,105 @@ def generate_tilemap(files, directory, gw, gh):
67
  elif ext in vid_exts:
68
  assets += (f'<video id="{stem}" '
69
  f'src="data:video/mp4;base64,{data}" '
70
- 'loop="true" autoplay="true" muted="true"></video>')
71
  assets += "</a-assets>"
72
 
73
- # --- Spawn per-cell, now fully random ---
74
  entities = ""
75
- sx = -gw/2
76
- sz = -gh/2
77
-
78
  for i in range(gw):
79
  for j in range(gh):
80
- x = sx + i
81
- z = sz + j
82
-
83
- # 1) ground image plane
84
- if img_files:
85
- img = random.choice(img_files)
86
- stem = Path(img).stem
87
- entities += (f'<a-plane src="#{stem}" width="1" height="1" '
88
  f'rotation="-90 0 0" position="{x} 0.01 {z}"></a-plane>')
89
-
90
- # 2) random spinning 3D model
91
- if model_files:
92
- mdl = random.choice(model_files)
93
- ext = mdl.split('.')[-1]
94
- stem = Path(mdl).stem
95
- entities += create_aframe_entity(stem, ext, f"{x} 0.5 {z}")
96
-
97
- # 3) random video layer
98
- if vid_files:
99
- vid = random.choice(vid_files)
100
- stem = Path(vid).stem
101
- entities += (f'<a-video src="#{stem}" width="1" height="1" '
102
  f'rotation="-90 0 0" position="{x} 0.2 {z}" '
103
  'class="raycastable" draggable></a-video>')
104
-
105
  return assets, entities
106
 
107
  def main():
108
  st.set_page_config(layout="wide")
109
  with st.sidebar:
110
- # your unchanged sidebar UI (uploads, camera buttons, grid sliders, directory)
111
- st.markdown("### 🗺️ Grid Size")
112
- grid_width = st.slider("Grid Width", 1, 8, 8)
113
- grid_height = st.slider("Grid Height", 1, 5, 5)
 
 
 
 
 
 
 
 
 
 
114
  st.markdown("### 📁 Directory")
115
- directory = st.text_input("Enter path:", ".", key="directory_input")
116
 
117
  if not os.path.isdir(directory):
118
- st.sidebar.error("Invalid directory path")
119
  return
120
 
121
- file_types = ['obj','glb','gltf','webp','png','mp4']
122
- files = [f for f in os.listdir(directory) if f.split('.')[-1] in file_types]
 
 
 
 
 
 
 
 
123
 
124
- # Compute spotlight height
125
- spot_height = max(grid_width, grid_height) * 1.5
126
 
127
- # — Build A-Frame scene shell with new spotlight
128
- aframe_scene = f"""
 
 
 
129
  <a-scene embedded style="height:600px; width:100%;">
130
- <a-entity id="rig" position="0 {max(grid_width,grid_height)} 0" rotation="-90 0 0">
131
- <a-camera fov="60" look-controls cursor="rayOrigin: mouse" raycaster="objects:.raycastable"></a-camera>
 
 
132
  </a-entity>
133
  <a-sky color="#87CEEB"></a-sky>
134
-
135
  <!-- moving lights -->
136
  <a-entity moving-light="color:#FFD700; speed:0.07 0.05 0.06; bounds:4 3 4" position="2 2 -2"></a-entity>
137
  <a-entity moving-light="color:#FF6347; speed:0.06 0.08 0.05; bounds:4 3 4" position="-2 1 2"></a-entity>
138
  <a-entity moving-light="color:#00CED1; speed:0.05 0.06 0.07; bounds:4 3 4" position="0 3 0"></a-entity>
139
-
140
- <!-- ▪︎ New top-down spotlight ▪︎ -->
141
- <a-entity light="type: spot; color:#FFF; intensity:1; angle:45; penumbra:0.2"
142
- position="0 {spot_height} 0" rotation="-90 0 0">
143
- </a-entity>
144
-
145
- <a-text id="score" value="Score: 0" position="-1.5 1 -2" scale="0.5 0.5 0.5" color="white"></a-text>
146
  """
147
 
148
- assets, entities = generate_tilemap(files, directory, grid_width, grid_height)
149
- aframe_scene += assets + entities + "</a-scene>"
150
 
151
- # apply camera_move if any
152
- cam = st.session_state.get('camera_move')
153
- if cam:
154
- if cam == 'fire':
155
- aframe_scene += "<script>fireRaycast();</script>"
156
- else:
157
- aframe_scene += f"<script>moveCamera('{cam}');</script>"
158
- st.session_state.pop('camera_move')
159
 
160
  # loader for OBJ/glTF
161
  loader = '<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/aframe-extras.loaders.min.js"></script>'
162
 
163
  st.components.v1.html(
164
- load_aframe_and_extras() + loader + aframe_scene,
165
- height=630
166
  )
167
 
168
  if __name__ == "__main__":
 
9
  <script src="https://unpkg.com/[email protected]/dist/aframe-event-set-component.min.js"></script>
10
  <script>
11
  let score = 0;
12
+
13
+ /* Draggable, bouncing, moving-light components unchanged */
14
+ AFRAME.registerComponent('draggable', {/* … */});
15
+ AFRAME.registerComponent('bouncing', {/* … */});
16
+ AFRAME.registerComponent('moving-light', {/* … */});
17
+
18
+ function moveCamera(direction) {
19
+ var rig = document.querySelector('#rig');
20
+ var pos = rig.getAttribute('position');
21
+ var rot = rig.getAttribute('rotation');
22
+ var speed = 0.5, rSpeed = 5;
23
+ switch(direction) {
24
+ case 'forward': pos.z -= speed; break;
25
+ case 'backward': pos.z += speed; break;
26
+ case 'left': pos.x -= speed; break;
27
+ case 'right': pos.x += speed; break;
28
+ case 'up': pos.y += speed; break;
29
+ case 'down': pos.y -= speed; break;
30
+ case 'reset': pos = {x:0,y:1.6,z:0}; rot = {x:0,y:0,z:0}; break;
31
+ case 'rotateY+': rot.y += rSpeed; break;
32
+ case 'rotateY-': rot.y -= rSpeed; break;
33
+ case 'rotateZ+': rot.z += rSpeed; break;
34
+ case 'rotateZ-': rot.z -= rSpeed; break;
35
+ }
36
+ rig.setAttribute('position', pos);
37
+ rig.setAttribute('rotation', rot);
38
+ }
39
+
40
+ function fireRaycast() {
41
+ var cam = document.querySelector('[camera]');
42
+ var dir = new THREE.Vector3(); cam.object3D.getWorldDirection(dir);
43
+ var rc = new THREE.Raycaster(cam.object3D.position, dir);
44
+ var hits = rc.intersectObjects(
45
+ Array.from(document.querySelectorAll('.raycastable')).map(e=>e.object3D), true
46
+ );
47
+ if (hits.length>0) {
48
+ var el = hits[0].object.el;
49
+ if (el.components.bouncing) {
50
+ el.components.bouncing.boost();
51
+ score += 10;
52
+ document.getElementById('score').setAttribute('value','Score: '+score);
53
+ }
54
+ }
55
+ }
56
+
57
+ document.addEventListener('keydown', e => {
58
+ switch(e.key.toLowerCase()){
59
+ case 'w': moveCamera('forward'); break;
60
+ case 'x': moveCamera('backward'); break;
61
+ case 'a': moveCamera('left'); break;
62
+ case 'd': moveCamera('right'); break;
63
+ case 's': moveCamera('reset'); break;
64
+ case 'q': moveCamera('rotateY+'); break;
65
+ case 'e': moveCamera('rotateY-'); break;
66
+ case 'z': moveCamera('rotateZ+'); break;
67
+ case 'c': moveCamera('rotateZ-'); break;
68
+ case ' ': fireRaycast(); break;
69
  }
70
  });
71
  </script>
72
  """
73
 
74
  @st.cache_data
75
+ def encode_file(path):
76
+ with open(path,'rb') as f: return base64.b64encode(f.read()).decode()
77
+
78
+ def create_aframe_entity(stem, ext, pos):
79
+ # static random Y rotation, scale fills 1×1 tile
80
+ ry = random.uniform(0,360)
81
+ if ext == 'obj':
82
+ return (f'<a-entity obj-model="obj:#{stem}" '
83
+ f'position="{pos}" rotation="0 {ry} 0" scale="1 1 1" '
84
+ 'class="raycastable" draggable></a-entity>')
85
+ if ext in ('glb','gltf'):
86
  return (f'<a-entity gltf-model="#{stem}" '
87
+ f'position="{pos}" rotation="0 {ry} 0" scale="1 1 1" '
88
+ 'class="raycastable" draggable></a-entity>')
89
+ return ''
90
 
91
  @st.cache_data
92
+ def generate_tilemap(files, dirpath, gw=8, gh=8):
93
  img_exts = ['webp','png']
94
  model_exts = ['obj','glb','gltf']
95
  vid_exts = ['mp4']
96
 
97
+ imgs = [f for f in files if f.split('.')[-1] in img_exts]
98
+ models = [f for f in files if f.split('.')[-1] in model_exts]
99
+ vids = [f for f in files if f.split('.')[-1] in vid_exts]
100
 
 
101
  assets = "<a-assets>"
102
  for f in files:
103
+ stem = Path(f).stem; ext=f.split('.')[-1]
104
+ data=encode_file(os.path.join(dirpath,f))
 
105
  if ext in model_exts:
106
  assets += (f'<a-asset-item id="{stem}" '
107
  f'src="data:application/octet-stream;base64,{data}"></a-asset-item>')
 
110
  elif ext in vid_exts:
111
  assets += (f'<video id="{stem}" '
112
  f'src="data:video/mp4;base64,{data}" '
113
+ 'loop autoplay muted></video>')
114
  assets += "</a-assets>"
115
 
 
116
  entities = ""
117
+ sx=-gw/2; sz=-gh/2
 
 
118
  for i in range(gw):
119
  for j in range(gh):
120
+ x,y,z = sx+i,0,sz+j
121
+ # ground image
122
+ if imgs:
123
+ img=random.choice(imgs); s=Path(img).stem
124
+ entities += (f'<a-plane src="#{s}" width="1" height="1" '
 
 
 
125
  f'rotation="-90 0 0" position="{x} 0.01 {z}"></a-plane>')
126
+ # model
127
+ if models:
128
+ m=random.choice(models); ext=m.split('.')[-1]; s=Path(m).stem
129
+ entities += create_aframe_entity(s,ext,f"{x} 0.5 {z}")
130
+ # video
131
+ if vids:
132
+ v=random.choice(vids); s=Path(v).stem
133
+ entities += (f'<a-video src="#{s}" width="1" height="1" '
 
 
 
 
 
134
  f'rotation="-90 0 0" position="{x} 0.2 {z}" '
135
  'class="raycastable" draggable></a-video>')
 
136
  return assets, entities
137
 
138
  def main():
139
  st.set_page_config(layout="wide")
140
  with st.sidebar:
141
+ st.markdown("### ⬆️ Upload 3D / media files")
142
+ ups = st.file_uploader("Add files:", accept_multiple_files=True)
143
+ st.markdown("### 🎮 Camera Buttons")
144
+ # 3×3 grid of buttons for qwe/asd/zxc
145
+ rows = [
146
+ [('Q','rotateY+'), ('W','forward'), ('E','rotateY-')],
147
+ [('A','left'), ('S','reset'), ('D','right')],
148
+ [('Z','rotateZ+'),('X','backward'),('C','rotateZ-')]
149
+ ]
150
+ for row in rows:
151
+ cols = st.columns(3)
152
+ for (btn,cmd),col in zip(row, cols):
153
+ col.button(btn, on_click=lambda c=cmd: st.session_state.update({'camera_move':c}))
154
+ st.markdown("### 🗺️ Grid is fixed at 8×8")
155
  st.markdown("### 📁 Directory")
156
+ directory = st.text_input("Path:", ".", key="dir")
157
 
158
  if not os.path.isdir(directory):
159
+ st.sidebar.error("Invalid directory")
160
  return
161
 
162
+ types = ['obj','glb','gltf','webp','png','mp4']
163
+ if ups:
164
+ for up in ups:
165
+ ext=Path(up.name).suffix.lower()[1:]
166
+ if ext in types:
167
+ with open(os.path.join(directory,up.name),'wb') as f:
168
+ shutil.copyfileobj(up,f)
169
+ st.sidebar.success(f"Uploaded {up.name}")
170
+ else:
171
+ st.sidebar.warning(f"Skipped {up.name}")
172
 
173
+ files = [f for f in os.listdir(directory) if f.split('.')[-1] in types]
 
174
 
175
+ # spotlight height
176
+ spot_h = max(8,8)*1.5
177
+
178
+ # build scene
179
+ scene = f"""
180
  <a-scene embedded style="height:600px; width:100%;">
181
+ <a-entity id="rig" position="0 1.6 0" rotation="0 0 0">
182
+ <a-camera fov="60" look-controls wasd-controls="enabled:true"
183
+ cursor="rayOrigin:mouse" raycaster="objects:.raycastable">
184
+ </a-camera>
185
  </a-entity>
186
  <a-sky color="#87CEEB"></a-sky>
 
187
  <!-- moving lights -->
188
  <a-entity moving-light="color:#FFD700; speed:0.07 0.05 0.06; bounds:4 3 4" position="2 2 -2"></a-entity>
189
  <a-entity moving-light="color:#FF6347; speed:0.06 0.08 0.05; bounds:4 3 4" position="-2 1 2"></a-entity>
190
  <a-entity moving-light="color:#00CED1; speed:0.05 0.06 0.07; bounds:4 3 4" position="0 3 0"></a-entity>
191
+ <!-- top-down spotlight -->
192
+ <a-entity light="type:spot; color:#FFF; intensity:1; angle:45; penumbra:0.2"
193
+ position="0 {spot_h} 0" rotation="-90 0 0"></a-entity>
194
+ <a-text id="score" value="Score:0" position="-1.5 2 -3" scale="0.5 0.5 0.5" color="white"></a-text>
 
 
 
195
  """
196
 
197
+ assets, ents = generate_tilemap(files, directory, 8, 8)
198
+ scene += assets + ents + "</a-scene>"
199
 
200
+ # apply button or key moves
201
+ cmd = st.session_state.get('camera_move')
202
+ if cmd:
203
+ scene += f"<script>moveCamera('{cmd}');</script>"
204
+ st.session_state.pop('camera_move')
 
 
 
205
 
206
  # loader for OBJ/glTF
207
  loader = '<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/aframe-extras.loaders.min.js"></script>'
208
 
209
  st.components.v1.html(
210
+ load_aframe_and_extras() + loader + scene,
211
+ height=650
212
  )
213
 
214
  if __name__ == "__main__":