{"id":1582,"date":"2016-11-10T05:27:01","date_gmt":"2016-11-10T05:27:01","guid":{"rendered":"http:\/\/sktperfectdemo.com\/?p=41"},"modified":"2025-12-10T08:45:05","modified_gmt":"2025-12-10T08:45:05","slug":"q-translate-x-3","status":"publish","type":"post","link":"https:\/\/adreamstudios.com\/?p=1582","title":{"rendered":"Q translate X"},"content":{"rendered":"\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\">\n  <title>My AR Model - AR with Audio<\/title>\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n\n  <!-- model-viewer (Google) -->\n  <script type=\"module\" src=\"https:\/\/unpkg.com\/@google\/model-viewer\/dist\/model-viewer.min.js\"><\/script>\n  <style>\n    body, html {\n      margin: 0; \n      padding: 0;\n      background: #000;\n      color: #fff;\n      font-family: system-ui, -apple-system, sans-serif;\n      overflow: hidden;\n    }\n    \n    model-viewer {\n      width: 100vw;\n      height: 100vh;\n      background: #000;\n    }\n    \n    .gps-status {\n      position: fixed;\n      top: 20px;\n      left: 50%;\n      transform: translateX(-50%);\n      background: rgba(0, 0, 0, 0.8);\n      color: #fff;\n      padding: 10px 20px;\n      border-radius: 25px;\n      z-index: 1000;\n      font-size: 14px;\n      display: none;\n    }\n    \n    .gps-status.active {\n      display: block;\n      background: rgba(40, 167, 69, 0.9);\n    }\n  <\/style>\n<\/head>\n<body>\n\n<model-viewer\n  id=\"ar-model\"\n  src=\"https:\/\/adreamstudios.com\/wp-content\/uploads\/gps-ar-files\/duck.glb\"\n  alt=\"My AR Model\"\n  ar\n  ar-modes=\"webxr scene-viewer quick-look\"\n  camera-controls\n  autoplay\n  exposure=\"1\"\n  shadow-intensity=\"1\"\n  environment-image=\"neutral\"\n  interaction-prompt=\"auto\"\n><\/model-viewer>\n\n<div id=\"gps-status\" class=\"gps-status\">\ud83d\udccd Checking GPS location...<\/div>\n\n<script type=\"module\">\n  import * as THREE from 'https:\/\/unpkg.com\/three@0.160.0\/build\/three.module.js';\n  import { GLTFLoader } from 'https:\/\/unpkg.com\/three@0.160.0\/examples\/jsm\/loaders\/GLTFLoader.js';\n  import { AudioLoader } from 'https:\/\/unpkg.com\/three@0.160.0\/examples\/jsm\/loaders\/AudioLoader.js';\n\n  const viewer = document.getElementById('ar-model');\n  const statusEl = document.getElementById('gps-status');\n  const listener = new THREE.AudioListener();\n  const audioLoader = new THREE.AudioLoader();\n  const loadedAudios = [];\n\n  \/\/ GPS configuration\n  const gpsConfig = {\n    lat: 0,\n    lng: 0,\n    radius: 100  };\n\n  \/\/ Helper to resolve audio URLs\n  function resolveAudioUrl(audioUrl, modelBaseUrl) {\n    if (audioUrl.startsWith('data:audio\/') || audioUrl.startsWith('data:')) {\n      return audioUrl;\n    }\n    if (audioUrl.startsWith('http:\/\/') || audioUrl.startsWith('https:\/\/') || audioUrl.startsWith('\/')) {\n      return audioUrl;\n    }\n    if (modelBaseUrl) {\n      const basePath = modelBaseUrl.substring(0, modelBaseUrl.lastIndexOf('\/') + 1);\n      return basePath + audioUrl;\n    }\n    return audioUrl;\n  }\n\n  \/\/ Process audio metadata from GLB\n  function processAudioMetadata(scene, modelBaseUrl) {\n    if (!listener || !audioLoader) {\n      console.warn('Audio listener or loader not initialized');\n      return;\n    }\n\n    scene.traverse((node) => {\n      let audioData = null;\n\n      \/\/ Check multiple locations for audio metadata\n      if (node.userData?.AR_audio) {\n        audioData = node.userData.AR_audio;\n      } else if (node.userData?.ar_audio) {\n        audioData = node.userData.ar_audio;\n      } else if (node.userData?.extras?.AR_audio) {\n        audioData = node.userData.extras.AR_audio;\n      } else if (node.userData?.extras?.ar_audio) {\n        audioData = node.userData.extras.ar_audio;\n      } else if (node.extras?.AR_audio) {\n        audioData = node.extras.AR_audio;\n      } else if (node.extras?.ar_audio) {\n        audioData = node.extras.ar_audio;\n      }\n\n      \/\/ Handle array of audio data\n      if (Array.isArray(audioData)) {\n        audioData.forEach(data => processSingleAudio(data, node, modelBaseUrl));\n      } else if (audioData) {\n        processSingleAudio(audioData, node, modelBaseUrl);\n      }\n    });\n  }\n\n  \/\/ Process a single audio configuration\n  function processSingleAudio(audioData, node, modelBaseUrl) {\n    const audioUrl = audioData.url || audioData.uri;\n    if (!audioUrl) {\n      console.warn('Audio data found but no URL\/URI specified:', audioData);\n      return;\n    }\n\n    console.log('Processing audio:', audioUrl, 'for node:', node.name || 'unnamed');\n\n    const resolvedUrl = resolveAudioUrl(audioUrl, modelBaseUrl);\n    const isPositional = audioData.positional !== false;\n\n    const sound = isPositional \n      ? new THREE.PositionalAudio(listener)\n      : new THREE.Audio(listener);\n\n    sound.setLoop(audioData.loop !== false);\n    sound.setVolume(audioData.volume !== undefined ? audioData.volume : 1.0);\n\n    if (isPositional) {\n      sound.setRefDistance(audioData.refDistance || audioData.ref_distance || 1.0);\n      sound.setMaxDistance(audioData.maxDistance || audioData.max_distance || 20.0);\n      sound.setRolloffFactor(audioData.rolloffFactor || audioData.rolloff_factor || 1.0);\n\n      if (audioData.coneInnerAngle !== undefined) {\n        sound.setConeInnerAngle(audioData.coneInnerAngle);\n      }\n      if (audioData.coneOuterAngle !== undefined) {\n        sound.setConeOuterAngle(audioData.coneOuterAngle);\n      }\n      if (audioData.coneOuterGain !== undefined) {\n        sound.setConeOuterGain(audioData.coneOuterGain);\n      }\n    }\n\n    audioLoader.load(\n      resolvedUrl,\n      (buffer) => {\n        sound.setBuffer(buffer);\n        console.log('Audio buffer loaded successfully:', resolvedUrl);\n        node.add(sound);\n        loadedAudios.push(sound);\n\n        \/\/ Check GPS before auto-playing\n        const isGPSUnlocked = !gpsConfig.lat || !gpsConfig.lng || checkGPSLocation();\n        if (audioData.autoplay !== false && isGPSUnlocked) {\n          sound.play().catch((error) => {\n            console.warn('Error auto-playing audio (may require user interaction):', error);\n          });\n        }\n      },\n      undefined,\n      (error) => {\n        console.error('Error loading audio:', resolvedUrl, error);\n      }\n    );\n  }\n\n  \/\/ GPS location checking\n  function calculateDistance(lat1, lon1, lat2, lon2) {\n    const R = 6371000;\n    const dLat = (lat2 - lat1) * Math.PI \/ 180;\n    const dLon = (lon2 - lon1) * Math.PI \/ 180;\n    const a =\n      Math.sin(dLat \/ 2) * Math.sin(dLat \/ 2) +\n      Math.cos(lat1 * Math.PI \/ 180) * Math.cos(lat2 * Math.PI \/ 180) *\n      Math.sin(dLon \/ 2) * Math.sin(dLon \/ 2);\n    const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));\n    return R * c;\n  }\n\n  function checkGPSLocation() {\n    if (!gpsConfig.lat || !gpsConfig.lng) {\n      return true; \/\/ No GPS restriction\n    }\n\n    return new Promise((resolve) => {\n      if (!navigator.geolocation) {\n        resolve(false);\n        return;\n      }\n\n      navigator.geolocation.getCurrentPosition(\n        (position) => {\n          const distance = calculateDistance(\n            position.coords.latitude,\n            position.coords.longitude,\n            gpsConfig.lat,\n            gpsConfig.lng\n          );\n\n          const within = distance <= gpsConfig.radius;\n          if (within) {\n            statusEl.textContent = '\u2705 GPS unlocked - AR ready';\n            statusEl.classList.add('active');\n            setTimeout(() => {\n              statusEl.style.display = 'none';\n            }, 3000);\n            resolve(true);\n          } else {\n            statusEl.textContent = `\ud83d\udccd Move closer (${Math.round(distance)}m away)`;\n            statusEl.style.display = 'block';\n            statusEl.classList.remove('active');\n            resolve(false);\n          }\n        },\n        () => {\n          statusEl.textContent = '\u26a0\ufe0f Location permission needed';\n          statusEl.style.display = 'block';\n          statusEl.classList.remove('active');\n          resolve(false);\n        }\n      );\n    });\n  }\n\n  \/\/ Load GLB for audio processing\n  function loadGLBForAudio(modelUrl) {\n    const loader = new GLTFLoader();\n    loader.load(\n      modelUrl,\n      (gltf) => {\n        console.log('GLB loaded for audio processing:', modelUrl);\n        processAudioMetadata(gltf.scene, modelUrl);\n      },\n      undefined,\n      (error) => {\n        console.warn('Could not load GLB for audio processing:', error);\n      }\n    );\n  }\n\n  \/\/ Initialize audio when model loads\n  if (viewer) {\n    viewer.addEventListener('scene-graph-ready', () => {\n      console.log('Model scene graph ready, processing audio...');\n      const modelUrl = viewer.getAttribute('src');\n      if (modelUrl) {\n        loadGLBForAudio(modelUrl);\n      }\n    });\n\n    viewer.addEventListener('load', () => {\n      console.log('Model loaded, checking for audio...');\n      const modelUrl = viewer.getAttribute('src');\n      if (modelUrl) {\n        loadGLBForAudio(modelUrl);\n      }\n    });\n\n    viewer.addEventListener('ar-status', (event) => {\n      if (event.detail.status === 'session-started') {\n        console.log('AR session started, playing audio if available...');\n        checkGPSLocation().then((unlocked) => {\n          if (unlocked) {\n            loadedAudios.forEach((sound) => {\n              if (sound && !sound.isPlaying) {\n                sound.play().catch((error) => {\n                  console.warn('Error playing audio on AR start:', error);\n                });\n              }\n            });\n          }\n        });\n      } else if (event.detail.status === 'session-ended') {\n        console.log('AR session ended, stopping audio...');\n        loadedAudios.forEach((sound) => {\n          if (sound && sound.isPlaying) {\n            sound.stop();\n          }\n        });\n      }\n    });\n  }\n\n  \/\/ Check GPS on page load if GPS is configured\n  if (gpsConfig.lat && gpsConfig.lng) {\n    checkGPSLocation();\n    setInterval(checkGPSLocation, 5000); \/\/ Check every 5 seconds\n  }\n\n  \/\/ Cleanup on page unload\n  window.addEventListener('beforeunload', () => {\n    loadedAudios.forEach((sound) => {\n      if (sound && sound.isPlaying) {\n        sound.stop();\n      }\n    });\n  });\n\n  console.log('GPS AR Audio Demo initialized');\n<\/script>\n\n<\/body>\n<\/html>\n\n","protected":false},"excerpt":{"rendered":"","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_kad_post_transparent":"","_kad_post_title":"","_kad_post_layout":"","_kad_post_sidebar_id":"","_kad_post_content_style":"","_kad_post_vertical_padding":"","_kad_post_feature":"","_kad_post_feature_position":"","_kad_post_header":false,"_kad_post_footer":false,"_kad_post_classname":"","footnotes":""},"categories":[1],"tags":[],"class_list":["post-1582","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/adreamstudios.com\/index.php?rest_route=\/wp\/v2\/posts\/1582","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/adreamstudios.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/adreamstudios.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/adreamstudios.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/adreamstudios.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1582"}],"version-history":[{"count":1,"href":"https:\/\/adreamstudios.com\/index.php?rest_route=\/wp\/v2\/posts\/1582\/revisions"}],"predecessor-version":[{"id":1817,"href":"https:\/\/adreamstudios.com\/index.php?rest_route=\/wp\/v2\/posts\/1582\/revisions\/1817"}],"wp:attachment":[{"href":"https:\/\/adreamstudios.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1582"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/adreamstudios.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1582"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/adreamstudios.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1582"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}