mkfallah commited on
Commit
9ab730c
·
verified ·
1 Parent(s): fd4f4c8

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +422 -17
index.html CHANGED
@@ -1,19 +1,424 @@
 
1
  <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  </html>
 
1
+ <!-- webxr-ar-demo.html -->
2
  <!doctype html>
3
+ <html lang="fa">
4
+ <head>
5
+ <meta charset="utf-8" />
6
+ <meta name="viewport" content="width=device-width,initial-scale=1.0,viewport-fit=cover" />
7
+ <title>WebXR AR Demo — Floating Marker</title>
8
+ <style>
9
+ :root{
10
+ --bg:#0f1724;
11
+ --card:#0b1220;
12
+ --accent:#4dd0e1;
13
+ --muted:#9aa6b2;
14
+ --glass: rgba(255,255,255,0.04);
15
+ --radius: 12px;
16
+ }
17
+ html,body{height:100%;margin:0;background:linear-gradient(180deg,#071124,#0f1724);font-family:Inter,system-ui,-apple-system,Segoe UI,Roboto,"Helvetica Neue",Arial;}
18
+ #app{height:100%;display:flex;flex-direction:column;align-items:stretch;}
19
+ #overlayUI{position:fixed;left:12px;right:12px;top:12px;display:flex;justify-content:space-between;gap:12px;z-index:50}
20
+ .card{backdrop-filter:blur(6px);background:var(--glass);padding:8px;border-radius:999px;display:flex;gap:8px;align-items:center;}
21
+ button{background:transparent;border:1px solid rgba(255,255,255,0.06);color:white;padding:8px 12px;border-radius:10px;font-weight:600}
22
+ button.primary{background:linear-gradient(90deg,var(--accent),#7bdff0);color:#052026;border:0}
23
+ #hint{position:fixed;left:12px;bottom:16px;right:12px;color:var(--muted);font-size:13px;text-align:center;z-index:40}
24
+ #message{position:fixed;left:50%;transform:translateX(-50%);top:60px;background:rgba(0,0,0,0.5);padding:8px 12px;border-radius:10px;color:white;z-index:60}
25
+ canvas{touch-action:none}
26
+ /* small info card */
27
+ #infoCard{position:fixed;right:12px;top:70px;background:linear-gradient(180deg,#071a23,#0b1620);padding:12px;border-radius:12px;color:var(--muted);font-size:13px;z-index:60;max-width:260px}
28
+ #logo{display:flex;gap:8px;align-items:center}
29
+ #logo .dot{width:10px;height:10px;border-radius:50%;background:var(--accent);box-shadow:0 0 10px var(--accent)}
30
+ </style>
31
+ </head>
32
+ <body>
33
+ <div id="app">
34
+ <div id="overlayUI">
35
+ <div class="card">
36
+ <div id="logo"><div class="dot"></div><div style="font-weight:700">AR-Demo</div></div>
37
+ </div>
38
+ <div class="card" id="controls">
39
+ <button id="btnStart" class="primary">start ar</button>
40
+ <button id="btnReset">reset</button>
41
+ <button id="btnFallback">demo fallback</button>
42
+ </div>
43
+ </div>
44
+
45
+ <div id="message" style="display:none"></div>
46
+ <div id="infoCard">
47
+ <div style="font-weight:700;color:white">Floating Marker — demo</div>
48
+ <div style="margin-top:8px">نحوهٔ کار: روی start ar بزن. سپس صفحهٔ مرورگر اجازهٔ دسترسی به دوربین را می‌پرسد. گوشی را حرکت بده تا شیء در فضا دیده شود. با تپ روی شیء رنگ عوض می‌شود.</div>
49
+ </div>
50
+
51
+ <div id="container"></div>
52
+ <div id="hint">best on Chrome (Android). requires https and camera permission.</div>
53
+ </div>
54
+
55
+ <!-- three.js and helpers (es modules) -->
56
+ <script type="module">
57
+ // imports
58
+ import * as THREE from 'https://unpkg.com/three@0.152.2/build/three.module.js';
59
+ import { ARButton } from 'https://unpkg.com/three@0.152.2/examples/jsm/webxr/ARButton.js';
60
+ import { XRControllerModelFactory } from 'https://unpkg.com/three@0.152.2/examples/jsm/webxr/XRControllerModelFactory.js';
61
+
62
+ // ui elements
63
+ const btnStart = document.getElementById('btnStart');
64
+ const btnReset = document.getElementById('btnReset');
65
+ const btnFallback = document.getElementById('btnFallback');
66
+ const message = document.getElementById('message');
67
+ const container = document.getElementById('container');
68
+
69
+ // scene setup variables
70
+ let camera, scene, renderer;
71
+ let reticle = null; // small marker
72
+ let markerGroup = null;
73
+ let clock = new THREE.Clock();
74
+ let xrSessionActive = false;
75
+ let currentColorIndex = 0;
76
+ const colors = [0x4dd0e1, 0x7b61ff, 0xff7ab6, 0x6ee07a];
77
+
78
+ // show temporary messages to user
79
+ function showMsg(text, timeout=2500){
80
+ message.style.display='block';
81
+ message.textContent = text;
82
+ clearTimeout(message._t);
83
+ message._t = setTimeout(()=> message.style.display='none', timeout);
84
+ }
85
+
86
+ // basic three scene (not yet xr-enabled)
87
+ function initThree(){
88
+ // create renderer
89
+ renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
90
+ renderer.setPixelRatio(window.devicePixelRatio);
91
+ renderer.setSize(window.innerWidth, window.innerHeight);
92
+ renderer.xr.enabled = true;
93
+ container.appendChild(renderer.domElement);
94
+
95
+ // create scene and camera (camera will be controlled by XR)
96
+ scene = new THREE.Scene();
97
+ camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.01, 100);
98
+
99
+ // subtle ambient and a directional light for modern look
100
+ const ambient = new THREE.HemisphereLight(0xffffff, 0x444444, 0.6);
101
+ scene.add(ambient);
102
+ const dir = new THREE.DirectionalLight(0xffffff, 0.8);
103
+ dir.position.set(0.5, 1, 0.2);
104
+ scene.add(dir);
105
+
106
+ // marker group: a floating sphere + halo + label
107
+ markerGroup = new THREE.Group();
108
+ markerGroup.visible = false;
109
+
110
+ // sphere
111
+ const sphereGeo = new THREE.SphereGeometry(0.12, 32, 24);
112
+ const sphereMat = new THREE.MeshStandardMaterial({ metalness:0.3, roughness:0.2, emissive:colors[0], emissiveIntensity:0.2 });
113
+ const sphere = new THREE.Mesh(sphereGeo, sphereMat);
114
+ sphere.name = 'markerSphere';
115
+
116
+ // halo (a thin torus)
117
+ const torusGeo = new THREE.TorusGeometry(0.18, 0.02, 16, 100);
118
+ const torusMat = new THREE.MeshBasicMaterial({ color: colors[0], transparent:true, opacity:0.9 });
119
+ const halo = new THREE.Mesh(torusGeo, torusMat);
120
+ halo.rotation.x = Math.PI / 2;
121
+ halo.position.y = -0.02;
122
+
123
+ // floating label (flat plane with basic text using canvas texture)
124
+ const labelCanvas = document.createElement('canvas');
125
+ labelCanvas.width = 512; labelCanvas.height = 128;
126
+ const ctx = labelCanvas.getContext('2d');
127
+ function updateLabel(text){
128
+ ctx.clearRect(0,0,labelCanvas.width,labelCanvas.height);
129
+ ctx.fillStyle = 'rgba(5,32,38,0.9)';
130
+ ctx.fillRect(0,0,labelCanvas.width,labelCanvas.height);
131
+ ctx.font = 'bold 46px system-ui';
132
+ ctx.fillStyle = 'white';
133
+ ctx.textAlign = 'center';
134
+ ctx.fillText(text, labelCanvas.width/2, 76);
135
+ labelTexture.needsUpdate = true;
136
+ }
137
+ const labelTexture = new THREE.CanvasTexture(labelCanvas);
138
+ const labelMat = new THREE.MeshBasicMaterial({ map: labelTexture, transparent:false });
139
+ const labelGeo = new THREE.PlaneGeometry(0.6, 0.15);
140
+ const labelMesh = new THREE.Mesh(labelGeo, labelMat);
141
+ labelMesh.position.y = 0.28;
142
+ labelMesh.lookAt(0,0,0); // will be updated each frame
143
+
144
+ updateLabel('ورودی اصلی');
145
+
146
+ // add to group
147
+ markerGroup.add(sphere);
148
+ markerGroup.add(halo);
149
+ markerGroup.add(labelMesh);
150
+ scene.add(markerGroup);
151
+
152
+ // reticle (small ring used when hit-test available)
153
+ const ringGeo = new THREE.RingGeometry(0.07, 0.09, 32);
154
+ const ringMat = new THREE.MeshBasicMaterial({ color:0xffffff, side:THREE.DoubleSide, transparent:true, opacity:0.85 });
155
+ reticle = new THREE.Mesh(ringGeo, ringMat);
156
+ reticle.rotation.x = -Math.PI / 2;
157
+ reticle.visible = false;
158
+ scene.add(reticle);
159
+
160
+ window.addEventListener('resize', onWindowResize);
161
+ }
162
+
163
+ function onWindowResize(){
164
+ if (camera){
165
+ camera.aspect = window.innerWidth / window.innerHeight;
166
+ camera.updateProjectionMatrix();
167
+ }
168
+ if (renderer) renderer.setSize(window.innerWidth, window.innerHeight);
169
+ }
170
+
171
+ // set marker at given position and make visible
172
+ function placeMarkerAt(pos, quaternion){
173
+ markerGroup.position.copy(pos);
174
+ if (quaternion) markerGroup.quaternion.copy(quaternion);
175
+ markerGroup.visible = true;
176
+ showMsg('marker placed');
177
+ }
178
+
179
+ // change marker color (on tap)
180
+ function cycleMarkerColor(){
181
+ currentColorIndex = (currentColorIndex + 1) % colors.length;
182
+ const sphere = markerGroup.getObjectByName('markerSphere');
183
+ sphere.material.emissive.setHex(colors[currentColorIndex]);
184
+ markerGroup.children.forEach(child=>{
185
+ if (child.material && child.material.color){
186
+ child.material.color.setHex(colors[currentColorIndex]);
187
+ }
188
+ });
189
+ }
190
+
191
+ // animate floating and halo
192
+ function animateMarker(delta){
193
+ if (!markerGroup.visible) return;
194
+ const t = performance.now() * 0.001;
195
+ markerGroup.position.y += Math.sin(t*1.5) * 0.0008; // tiny float
196
+ // rotate halo slowly
197
+ const halo = markerGroup.children.find(c=>c.geometry && c.geometry.type === 'TorusGeometry');
198
+ if (halo) halo.rotation.z += delta * 0.6;
199
+ // pulse emissive
200
+ const sphere = markerGroup.getObjectByName('markerSphere');
201
+ sphere.material.emissiveIntensity = 0.2 + Math.abs(Math.sin(t*2)) * 0.5;
202
+ }
203
+
204
+ // start a WebXR AR session via three's ARButton
205
+ async function startARSession(){
206
+ if (!renderer) initThree();
207
+
208
+ // create AR button (hidden) to create session
209
+ const arButton = ARButton.createButton(renderer, { requiredFeatures: ['hit-test'] });
210
+ // programmatically click it to request permission and start session
211
+ arButton.style.display='none';
212
+ document.body.appendChild(arButton);
213
+ try {
214
+ const xrStarted = await new Promise((resolve, reject)=>{
215
+ // attach event listeners to detect session start
216
+ renderer.xr.addEventListener('sessionstart', ()=> resolve(true));
217
+ renderer.xr.addEventListener('sessionend', ()=> resolve(false));
218
+ arButton.click();
219
+ });
220
+ if (!xrStarted) {
221
+ showMsg('xr session not started');
222
+ return;
223
+ }
224
+ } catch(e){
225
+ console.error(e);
226
+ showMsg('failed to start xr: ' + (e && e.message ? e.message : e));
227
+ return;
228
+ }
229
+
230
+ // set renderer animation loop
231
+ renderer.setAnimationLoop(render);
232
+
233
+ // request hit test source if available
234
+ const session = renderer.xr.getSession();
235
+ xrSessionActive = true;
236
+
237
+ // preference: use viewer space for hit-test
238
+ let viewerSpace = await session.requestReferenceSpace('viewer').catch(()=>null);
239
+ if (viewerSpace){
240
+ const hitTestSource = await session.requestHitTestSource({ space: viewerSpace }).catch(()=>null);
241
+ // on each frame, we'll use hit test results to position reticle
242
+ session.requestAnimationFrame(function onXRFrame(time, frame){
243
+ // nothing here, actual rendering handled in render loop
244
+ });
245
+ // store in session for use in render
246
+ session._hitTestSource = hitTestSource;
247
+ } else {
248
+ // no viewer space/hit-test available – we'll fallback to fixed distance placement
249
+ showMsg('hit-test not available, using fixed distance placement');
250
+ }
251
+
252
+ showMsg('webxr session started', 2000);
253
+
254
+ // controller for tap events
255
+ const controller = renderer.xr.getController(0);
256
+ controller.addEventListener('select', onSelect);
257
+ scene.add(controller);
258
+ }
259
+
260
+ // select handler when in XR (tap on screen)
261
+ function onSelect(){
262
+ // prefer reticle if visible
263
+ if (reticle && reticle.visible){
264
+ const pos = new THREE.Vector3();
265
+ pos.setFromMatrixPosition(reticle.matrix);
266
+ // also get orientation from camera
267
+ const quat = new THREE.Quaternion();
268
+ quat.setFromRotationMatrix(reticle.matrix);
269
+ placeMarkerAt(pos, quat);
270
+ return;
271
+ }
272
+ // else place at fixed distance in front of camera (1.5m)
273
+ const cam = renderer.xr.getCamera(camera);
274
+ const dir = new THREE.Vector3(0,0,-1).applyQuaternion(cam.quaternion);
275
+ const pos = new THREE.Vector3().copy(cam.position).add(dir.multiplyScalar(1.5));
276
+ placeMarkerAt(pos, null);
277
+ }
278
+
279
+ // render loop
280
+ function render(timestamp, frame){
281
+ const delta = clock.getDelta();
282
+
283
+ // handle hit-test if possible
284
+ if (frame){
285
+ const session = renderer.xr.getSession();
286
+ if (session && session._hitTestSource){
287
+ const hitTestResults = frame.getHitTestResults(session._hitTestSource);
288
+ if (hitTestResults && hitTestResults.length > 0){
289
+ const hit = hitTestResults[0];
290
+ const pose = hit.getPose(renderer.xr.getReferenceSpace());
291
+ reticle.visible = true;
292
+ reticle.matrix.fromArray(pose.transform.matrix);
293
+ reticle.matrix.decompose(reticle.position, reticle.quaternion, reticle.scale);
294
+ } else {
295
+ reticle.visible = false;
296
+ }
297
+ } else {
298
+ // no hit test — position reticle in front of camera at fixed distance as visual guide
299
+ const cam = renderer.xr.getCamera(camera);
300
+ reticle.visible = true;
301
+ const dir = new THREE.Vector3(0,0,-1).applyQuaternion(cam.quaternion);
302
+ reticle.position.copy(cam.position).add(dir.multiplyScalar(1.6));
303
+ reticle.quaternion.copy(cam.quaternion);
304
+ }
305
+ } else {
306
+ // non-xr rendering fallback path (preview mode)
307
+ }
308
+
309
+ // update marker animation
310
+ animateMarker(delta);
311
+
312
+ // billboard the label to face camera
313
+ if (markerGroup.visible){
314
+ const label = markerGroup.children.find(c=>c.geometry && c.geometry.type === 'PlaneGeometry');
315
+ if (label) label.lookAt(renderer.xr.isPresenting ? renderer.xr.getCamera(camera).position : camera.position);
316
+ }
317
+
318
+ renderer.render(scene, camera);
319
+ }
320
+
321
+ // reset scene marker
322
+ function resetMarker(){
323
+ if (markerGroup) markerGroup.visible=false;
324
+ showMsg('reset');
325
+ }
326
+
327
+ // simple fallback demo using getUserMedia if WebXR not supported
328
+ async function startFallbackDemo(){
329
+ // init three if not already
330
+ if (!renderer) initThree();
331
+ // remove any XR session if present
332
+ if (renderer.xr && renderer.xr.getSession()){
333
+ try { await renderer.xr.getSession().end(); } catch(e){/*ignore*/}
334
+ }
335
+ // ask for camera permission via getUserMedia and set as video background
336
+ const stream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: 'environment' }, audio:false });
337
+ const video = document.createElement('video');
338
+ video.autoplay = true; video.playsInline = true; video.muted = true;
339
+ video.srcObject = stream;
340
+ await video.play();
341
+
342
+ // create video background as a three texture-mapped plane behind the scene
343
+ const videoTex = new THREE.VideoTexture(video);
344
+ videoTex.minFilter = THREE.LinearFilter; videoTex.magFilter = THREE.LinearFilter; videoTex.format = THREE.RGBFormat;
345
+ // place a large plane behind
346
+ const bgMat = new THREE.MeshBasicMaterial({ map: videoTex });
347
+ const bgGeo = new THREE.PlaneGeometry(2, 2 * (window.innerHeight / window.innerWidth));
348
+ const bgMesh = new THREE.Mesh(bgGeo, bgMat);
349
+ bgMesh.position.set(0,0,-1.5);
350
+ scene.add(bgMesh);
351
+
352
+ // show instructions
353
+ showMsg('demo mode: tap screen to place floating marker');
354
+
355
+ // simple tap handler for placement on center ray
356
+ renderer.domElement.style.touchAction = 'auto';
357
+ renderer.domElement.addEventListener('pointerdown', (e)=>{
358
+ // place a marker in front of camera at 1.4m
359
+ const pos = new THREE.Vector3(0,0,-1).applyQuaternion(camera.quaternion).multiplyScalar(1.4).add(camera.position);
360
+ placeMarkerAt(pos);
361
+ });
362
+
363
+ // run the render loop without xr
364
+ renderer.setAnimationLoop(()=> {
365
+ const delta = clock.getDelta();
366
+ animateMarker(delta);
367
+ if (markerGroup.visible){
368
+ const label = markerGroup.children.find(c=>c.geometry && c.geometry.type === 'PlaneGeometry');
369
+ if (label) label.lookAt(camera.position);
370
+ }
371
+ renderer.render(scene,camera);
372
+ });
373
+ }
374
+
375
+ // event wiring
376
+ btnStart.addEventListener('click', async ()=>{
377
+ if (!navigator.xr){
378
+ showMsg('webxr not available on this browser');
379
+ return;
380
+ }
381
+ // initialize three if needed
382
+ if (!renderer) initThree();
383
+ try {
384
+ await startARSession();
385
+ } catch(e){
386
+ console.error(e);
387
+ showMsg('could not start webxr: ' + (e.message || e));
388
+ }
389
+ });
390
+
391
+ btnReset.addEventListener('click', ()=> resetMarker());
392
+ btnFallback.addEventListener('click', ()=> {
393
+ if (!renderer) initThree();
394
+ startFallbackDemo().catch(err=> {
395
+ console.error(err);
396
+ showMsg('fallback failed: ' + (err && err.message ? err.message : err));
397
+ });
398
+ });
399
+
400
+ // interaction: toggle color when tapping on marker in fallback or when in XR via select event
401
+ // add a pointerdown that raycasts to sphere (for fallback mode)
402
+ function setupPointerInteraction(){
403
+ const raycaster = new THREE.Raycaster();
404
+ const pointer = new THREE.Vector2();
405
+ renderer.domElement.addEventListener('pointerdown', (ev)=>{
406
+ // calculate pointer position
407
+ pointer.x = (ev.clientX / window.innerWidth) * 2 - 1;
408
+ pointer.y = - (ev.clientY / window.innerHeight) * 2 + 1;
409
+ raycaster.setFromCamera(pointer, camera);
410
+ const intersects = raycaster.intersectObjects(markerGroup.children, true);
411
+ if (intersects.length > 0){
412
+ cycleMarkerColor();
413
+ }
414
+ });
415
+ }
416
+
417
+ // initialize on load for quick demo
418
+ initThree();
419
+ setupPointerInteraction();
420
+ showMsg('ready — use start ar or demo fallback');
421
+
422
+ </script>
423
+ </body>
424
  </html>