Render image assets as textured planes in MapLibre demo.
CI / test (push) Successful in 4s

This loads image files via backend asset links (with auth when available), applies them as Three.js textures on plane meshes, and falls back to primitive placeholders if texture loading fails.

Made-with: Cursor
This commit is contained in:
2026-03-02 22:56:51 +00:00
parent c70d05b583
commit b1b11b47f7
+52
View File
@@ -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(