diff --git a/web/maplibre-demo.js b/web/maplibre-demo.js index 9c34905..54a6199 100644 --- a/web/maplibre-demo.js +++ b/web/maplibre-demo.js @@ -39,6 +39,7 @@ let threeCamera; const featureMeshes = new Map(); const modelTemplateCache = new Map(); const gltfLoader = new GLTFLoader(); +const textureLoader = new THREE.TextureLoader(); const ownFeatureMarkers = new Map(); const ownFeatureCoords = new Map(); let renderCycle = 0; @@ -143,6 +144,13 @@ function is3DAsset(asset) { return kind === "3d" && ext === "glb"; } +function isImageAsset(asset) { + if (!asset) return false; + const kind = String(asset.kind || "").toLowerCase(); + const ext = normalizeExt(asset.ext) || extFromLink(asset.link); + return kind === "image" && ["jpg", "jpeg", "png", "webp"].includes(ext); +} + async function renderSharedAssetFromQuery() { const params = new URLSearchParams(window.location.search); if (params.get("shared") !== "1") return; @@ -283,6 +291,42 @@ function addFallbackMesh(featureId, lng, lat, isPublic, kind) { map.triggerRepaint(); } +async function loadTextureFromAssetLink(assetLink) { + const assetURL = client.resolveRelativeLink(assetLink); + const headers = accessToken ? { Authorization: `Bearer ${accessToken}` } : undefined; + const response = await fetch(assetURL, { headers }); + if (!response.ok) { + throw new Error(`Failed to load image asset: HTTP ${response.status}`); + } + const blob = await response.blob(); + const objectURL = URL.createObjectURL(blob); + try { + const texture = await new Promise((resolve, reject) => { + textureLoader.load(objectURL, resolve, undefined, reject); + }); + texture.colorSpace = THREE.SRGBColorSpace; + texture.needsUpdate = true; + return texture; + } finally { + URL.revokeObjectURL(objectURL); + } +} + +async function addImagePlaneMesh(featureId, lng, lat, asset) { + const texture = await loadTextureFromAssetLink(asset.link); + const geometry = new THREE.PlaneGeometry(1.8, 1.8); + const material = new THREE.MeshBasicMaterial({ + map: texture, + transparent: true, + side: THREE.DoubleSide, + }); + const mesh = new THREE.Mesh(geometry, material); + positionObjectOnMap(mesh, lng, lat, 20); + threeScene.add(mesh); + featureMeshes.set(featureId, mesh); + map.triggerRepaint(); +} + function prepareModelRoot(modelRoot) { const box = new THREE.Box3().setFromObject(modelRoot); if (box.isEmpty()) return; @@ -344,6 +388,14 @@ async function addObjectMeshFromAsset(featureId, lng, lat, asset, cycleID) { } if (!is3DAsset(asset) || !asset.link) { + if (isImageAsset(asset) && asset.link) { + try { + await addImagePlaneMesh(featureId, lng, lat, asset); + return true; + } catch (error) { + console.warn(`Failed to load image texture for feature ${featureId}:`, error); + } + } const ext = normalizeExt(asset.ext) || extFromLink(asset.link); if (String(asset.kind || "").toLowerCase() === "3d" && ext === "gltf") { console.warn(