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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +43 -211
app.py CHANGED
@@ -9,140 +9,7 @@ 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
-
13
- AFRAME.registerComponent('draggable', {
14
- init: function () {
15
- this.el.setAttribute('class', 'raycastable');
16
- this.el.setAttribute('cursor-listener', '');
17
- this.dragHandler = this.dragMove.bind(this);
18
- this.el.sceneEl.addEventListener('mousemove', this.dragHandler);
19
- this.el.addEventListener('mousedown', this.onDragStart.bind(this));
20
- this.el.addEventListener('mouseup', this.onDragEnd.bind(this));
21
- this.camera = document.querySelector('[camera]');
22
- },
23
- remove: function () {
24
- this.el.removeAttribute('cursor-listener');
25
- this.el.sceneEl.removeEventListener('mousemove', this.dragHandler);
26
- },
27
- onDragStart: function (evt) {
28
- this.isDragging = true;
29
- this.el.emit('dragstart');
30
- },
31
- onDragEnd: function (evt) {
32
- this.isDragging = false;
33
- this.el.emit('dragend');
34
- },
35
- dragMove: function (evt) {
36
- if (!this.isDragging) return;
37
- var camera = this.camera;
38
- var vector = new THREE.Vector3(
39
- evt.clientX / window.innerWidth * 2 - 1,
40
- -(evt.clientY / window.innerHeight) * 2 + 1,
41
- 0.5
42
- );
43
- vector.unproject(camera);
44
- var dir = vector.sub(camera.position).normalize();
45
- var distance = -camera.position.y / dir.y;
46
- var pos = camera.position.clone().add(dir.multiplyScalar(distance));
47
- this.el.setAttribute('position', pos);
48
- }
49
- });
50
-
51
- AFRAME.registerComponent('bouncing', {
52
- schema: {
53
- speed: {type: 'vec3', default: {x: 0.1, y: 0.1, z: 0.1}},
54
- dist: {type: 'vec3', default: {x: 0.5, y: 0.5, z: 0.5}}
55
- },
56
- init: function () {
57
- this.originalPos = this.el.getAttribute('position');
58
- this.dir = {x:1,y:1,z:1};
59
- },
60
- tick: function (time, timeDelta) {
61
- var p = this.el.getAttribute('position');
62
- var s = this.data.speed, d = this.data.dist;
63
- ['x','y','z'].forEach(a => {
64
- p[a] += s[a]*this.dir[a]*(timeDelta/1000);
65
- if (Math.abs(p[a] - this.originalPos[a]) > d[a]) {
66
- this.dir[a] *= -1;
67
- }
68
- });
69
- this.el.setAttribute('position', p);
70
- },
71
- boost: function() {
72
- var s = this.data.speed;
73
- ['x','y','z'].forEach(a => s[a]*=1.5);
74
- this.data.speed = s;
75
- this.dir = { x:Math.random()>0.5?1:-1,
76
- y:Math.random()>0.5?1:-1,
77
- z:Math.random()>0.5?1:-1 };
78
- }
79
- });
80
-
81
- AFRAME.registerComponent('moving-light', {
82
- schema: {
83
- color: {type:'color', default:'#FFF'},
84
- speed: {type:'vec3', default:{x:0.1,y:0.1,z:0.1}},
85
- bounds:{type:'vec3', default:{x:5,y:5,z:5}}
86
- },
87
- init: function(){
88
- this.dir = {x:1,y:1,z:1};
89
- this.light = document.createElement('a-light');
90
- this.light.setAttribute('type','point');
91
- this.light.setAttribute('color', this.data.color);
92
- this.light.setAttribute('intensity','0.75');
93
- this.el.appendChild(this.light);
94
- },
95
- tick: function(time, dt){
96
- var p = this.el.getAttribute('position'),
97
- s = this.data.speed,
98
- b = this.data.bounds;
99
- ['x','y','z'].forEach(a=>{
100
- p[a] += s[a]*this.dir[a]*(dt/1000);
101
- if (Math.abs(p[a])>b[a]) this.dir[a]*=-1;
102
- });
103
- this.el.setAttribute('position', p);
104
- }
105
- });
106
-
107
- function moveCamera(direction) {
108
- var rig = document.querySelector('#rig');
109
- var pos = rig.getAttribute('position');
110
- var rot = rig.getAttribute('rotation');
111
- var speed = 0.5, rSpeed = 5;
112
- switch(direction) {
113
- case 'up': pos.y += speed; break;
114
- case 'down': pos.y -= speed; break;
115
- case 'forward': pos.z -= speed; break;
116
- case 'left': pos.x -= speed; break;
117
- case 'right': pos.x += speed; break;
118
- case 'rotateLeft': rot.y += rSpeed; break;
119
- case 'rotateRight': rot.y -= rSpeed; break;
120
- case 'reset': pos = {x:0,y:10,z:0}; rot = {x:-90,y:0,z:0}; break;
121
- case 'ground': pos = {x:0,y:1.6,z:0}; rot = {x:0,y:0,z:0}; break;
122
- }
123
- rig.setAttribute('position', pos);
124
- rig.setAttribute('rotation', rot);
125
- }
126
-
127
- function fireRaycast() {
128
- var camera = document.querySelector('[camera]'),
129
- dir = new THREE.Vector3();
130
- camera.object3D.getWorldDirection(dir);
131
- var rc = new THREE.Raycaster();
132
- rc.set(camera.object3D.position, dir);
133
- var hits = rc.intersectObjects(
134
- document.querySelectorAll('.raycastable').map(e=>e.object3D), true
135
- );
136
- if (hits.length>0) {
137
- var el = hits[0].object.el;
138
- if (el.components.bouncing) {
139
- el.components.bouncing.boost();
140
- score += 10;
141
- document.getElementById('score').setAttribute('value','Score: '+score);
142
- }
143
- }
144
- }
145
-
146
  // —— Key remap: W→down, S→reset, X→up ——
147
  document.addEventListener('keydown', function(event){
148
  switch(event.key.toLowerCase()) {
@@ -165,20 +32,15 @@ def encode_file(file_path):
165
  return base64.b64encode(f.read()).decode()
166
 
167
  def create_aframe_entity(stem, file_type, position):
168
- """1×1×1 scale, spin around Y, draggable but no bouncing."""
169
  anim = 'animation="property: rotation; to: 0 360 0; loop: true; dur: 20000; easing: linear"'
170
  if file_type == 'obj':
171
- return (
172
- f'<a-entity obj-model="obj: #{stem}" '
173
- f'position="{position}" rotation="0 0 0" scale="1 1 1" '
174
- f'class="raycastable" draggable {anim}></a-entity>'
175
- )
176
  if file_type in ('glb','gltf'):
177
- return (
178
- f'<a-entity gltf-model="#{stem}" '
179
- f'position="{position}" rotation="0 0 0" scale="1 1 1" '
180
- f'class="raycastable" draggable {anim}></a-entity>'
181
- )
182
  return ""
183
 
184
  @st.cache_data
@@ -191,27 +53,24 @@ def generate_tilemap(files, directory, gw, gh):
191
  model_files = [f for f in files if f.split('.')[-1] in model_exts]
192
  vid_files = [f for f in files if f.split('.')[-1] in vid_exts]
193
 
 
194
  assets = "<a-assets>"
195
  for f in files:
196
  stem = Path(f).stem
197
  ext = f.split('.')[-1]
198
  data = encode_file(os.path.join(directory, f))
199
  if ext in model_exts:
200
- assets += (
201
- f'<a-asset-item id="{stem}" '
202
- f'src="data:application/octet-stream;base64,{data}">'
203
- '</a-asset-item>'
204
- )
205
  elif ext in img_exts:
206
  assets += f'<img id="{stem}" src="data:image/{ext};base64,{data}">'
207
  elif ext in vid_exts:
208
- assets += (
209
- f'<video id="{stem}" '
210
- f'src="data:video/mp4;base64,{data}" '
211
- 'loop="true" autoplay="true" muted="true"></video>'
212
- )
213
  assets += "</a-assets>"
214
 
 
215
  entities = ""
216
  sx = -gw/2
217
  sz = -gh/2
@@ -221,65 +80,37 @@ def generate_tilemap(files, directory, gw, gh):
221
  x = sx + i
222
  z = sz + j
223
 
224
- # 1) ground image
225
  if img_files:
226
- img = img_files[(i*gh + j) % len(img_files)]
227
- stem=Path(img).stem
228
- entities += (
229
- f'<a-plane src="#{stem}" width="1" height="1" '
230
- f'rotation="-90 0 0" position="{x} 0.01 {z}"></a-plane>'
231
- )
232
 
233
- # 2) spinning 3D model
234
  if model_files:
235
- mdl = model_files[(i*gh + j) % len(model_files)]
236
  ext = mdl.split('.')[-1]
237
- stem=Path(mdl).stem
238
  entities += create_aframe_entity(stem, ext, f"{x} 0.5 {z}")
239
 
240
- # 3) video layer
241
  if vid_files:
242
- vid = vid_files[(i*gh + j) % len(vid_files)]
243
  stem = Path(vid).stem
244
- entities += (
245
- f'<a-video src="#{stem}" width="1" height="1" '
246
- f'rotation="-90 0 0" position="{x} 0.2 {z}" '
247
- 'class="raycastable" draggable></a-video>'
248
- )
249
 
250
  return assets, entities
251
 
252
  def main():
253
  st.set_page_config(layout="wide")
254
  with st.sidebar:
255
- st.markdown("### 🤖 3D AI Using Claude 3.5 Sonnet for AI Pair Programming")
256
- st.markdown(
257
- "[Open 3D Animation Toolkit]"
258
- "(https://huggingface.co/spaces/awacke1/3d_animation_toolkit)",
259
- unsafe_allow_html=True
260
- )
261
- st.markdown("### ⬆️ Upload")
262
- uploaded_files = st.file_uploader("Add files:", accept_multiple_files=True, key="file_uploader")
263
-
264
- st.markdown("### 🎮 Camera Controls")
265
- col1, col2, col3 = st.columns(3)
266
- with col1:
267
- st.button("⬅️", on_click=lambda: st.session_state.update({'camera_move':'left'}))
268
- st.button("🔄↺", on_click=lambda: st.session_state.update({'camera_move':'rotateLeft'}))
269
- st.button("🔝", on_click=lambda: st.session_state.update({'camera_move':'reset'}))
270
- with col2:
271
- st.button("⬆️", on_click=lambda: st.session_state.update({'camera_move':'up'}))
272
- st.button("👀", on_click=lambda: st.session_state.update({'camera_move':'ground'}))
273
- st.button("🔫", on_click=lambda: st.session_state.update({'camera_move':'fire'}))
274
- with col3:
275
- st.button("➡️", on_click=lambda: st.session_state.update({'camera_move':'right'}))
276
- st.button("⬇️", on_click=lambda: st.session_state.update({'camera_move':'down'}))
277
- st.button("⏩", on_click=lambda: st.session_state.update({'camera_move':'forward'}))
278
-
279
  st.markdown("### 🗺️ Grid Size")
280
  grid_width = st.slider("Grid Width", 1, 8, 8)
281
- grid_height = st.slider("Grid Height",1, 5, 5)
282
-
283
  st.markdown("### 📁 Directory")
284
  directory = st.text_input("Enter path:", ".", key="directory_input")
285
 
@@ -288,35 +119,36 @@ def main():
288
  return
289
 
290
  file_types = ['obj','glb','gltf','webp','png','mp4']
291
- if uploaded_files:
292
- for up in uploaded_files:
293
- ext = Path(up.name).suffix.lower()[1:]
294
- if ext in file_types:
295
- with open(os.path.join(directory, up.name),"wb") as f:
296
- shutil.copyfileobj(up, f)
297
- st.sidebar.success(f"Uploaded: {up.name}")
298
- else:
299
- st.sidebar.warning(f"Skipped unsupported: {up.name}")
300
-
301
  files = [f for f in os.listdir(directory) if f.split('.')[-1] in file_types]
302
 
303
- # Build A-Frame scene shell
 
 
 
304
  aframe_scene = f"""
305
  <a-scene embedded style="height:600px; width:100%;">
306
  <a-entity id="rig" position="0 {max(grid_width,grid_height)} 0" rotation="-90 0 0">
307
  <a-camera fov="60" look-controls cursor="rayOrigin: mouse" raycaster="objects:.raycastable"></a-camera>
308
  </a-entity>
309
  <a-sky color="#87CEEB"></a-sky>
 
 
310
  <a-entity moving-light="color:#FFD700; speed:0.07 0.05 0.06; bounds:4 3 4" position="2 2 -2"></a-entity>
311
  <a-entity moving-light="color:#FF6347; speed:0.06 0.08 0.05; bounds:4 3 4" position="-2 1 2"></a-entity>
312
  <a-entity moving-light="color:#00CED1; speed:0.05 0.06 0.07; bounds:4 3 4" position="0 3 0"></a-entity>
 
 
 
 
 
 
313
  <a-text id="score" value="Score: 0" position="-1.5 1 -2" scale="0.5 0.5 0.5" color="white"></a-text>
314
  """
315
 
316
  assets, entities = generate_tilemap(files, directory, grid_width, grid_height)
317
  aframe_scene += assets + entities + "</a-scene>"
318
 
319
- # Apply camera_move if any
320
  cam = st.session_state.get('camera_move')
321
  if cam:
322
  if cam == 'fire':
@@ -325,7 +157,7 @@ def main():
325
  aframe_scene += f"<script>moveCamera('{cam}');</script>"
326
  st.session_state.pop('camera_move')
327
 
328
- # Loader for OBJ/glTF
329
  loader = '<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/aframe-extras.loaders.min.js"></script>'
330
 
331
  st.components.v1.html(
 
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()) {
 
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
 
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>')
 
 
 
65
  elif ext in img_exts:
66
  assets += f'<img id="{stem}" src="data:image/{ext};base64,{data}">'
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
 
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
 
 
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':
 
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(