174 lines
5.3 KiB
JavaScript
174 lines
5.3 KiB
JavaScript
(function () {
|
|
'use strict';
|
|
|
|
if (!document.querySelector('.customization-3d-wrapper')) return;
|
|
|
|
const get3DViewer = () => window.Customization3DViewer;
|
|
|
|
const load3DModel = (modelUrl, productId) => {
|
|
const viewer = get3DViewer();
|
|
return viewer ? viewer.loadModel(modelUrl, productId) : Promise.resolve(false);
|
|
};
|
|
|
|
const clear3DModel = () => {
|
|
const viewer = get3DViewer();
|
|
return viewer ? viewer.clearModel() : Promise.resolve();
|
|
};
|
|
|
|
const show3DEmpty = () => {
|
|
get3DViewer()?.showEmpty();
|
|
};
|
|
|
|
const get3DModelUrl = (productId, variantId, wrapper) => {
|
|
const viewer = get3DViewer();
|
|
return viewer ? viewer.getModelUrl(productId, variantId, wrapper) : Promise.resolve(null);
|
|
};
|
|
|
|
const get3DHotspots = () => {
|
|
const hotspots = window.CUSTOMIZATION_3D_HOTSPOTS;
|
|
if (!Array.isArray(hotspots)) return [];
|
|
return hotspots
|
|
.filter((item) => item && typeof item === 'object')
|
|
.map((item) => {
|
|
const meshName = String(item.meshName || '').trim();
|
|
if (!meshName) return null;
|
|
|
|
let offset = [0, 0, 0];
|
|
if (Array.isArray(item.offset) && item.offset.length >= 3) {
|
|
const parsed = item.offset.slice(0, 3).map((v) => Number(v));
|
|
if (parsed.every(Number.isFinite)) {
|
|
const maxAbs = Math.max(...parsed.map((v) => Math.abs(v)));
|
|
offset = parsed;
|
|
}
|
|
}
|
|
|
|
const next = {
|
|
id: String(item.id || meshName),
|
|
name: String(item.name || item.id || meshName),
|
|
meshName,
|
|
offset,
|
|
};
|
|
|
|
const color = String(item.color || '').trim();
|
|
if (/^#([0-9a-f]{3}|[0-9a-f]{6})$/i.test(color)) next.color = color;
|
|
|
|
const r = Number(item.radius);
|
|
if (Number.isFinite(r) && r > 0) {
|
|
next.radius = Math.min(Math.max(r, 0.5), 30);
|
|
} else {
|
|
const defaultRadius = Number(window.CUSTOMIZATION_3D_HOTSPOT_RADIUS_DEFAULT);
|
|
next.radius = Number.isFinite(defaultRadius) && defaultRadius > 0
|
|
? Math.min(30, defaultRadius)
|
|
: 18;
|
|
}
|
|
|
|
const icon = String(item.icon || '').trim();
|
|
if (icon && (/^https?:\/\//i.test(icon) || icon.startsWith('//'))) {
|
|
next.icon = icon;
|
|
}
|
|
|
|
if (item.payload && typeof item.payload === 'object' && !Array.isArray(item.payload)) {
|
|
next.payload = item.payload;
|
|
}
|
|
|
|
return next;
|
|
})
|
|
.filter(Boolean);
|
|
};
|
|
|
|
const getHotspotActionConfig = (detail = {}) => {
|
|
const all = window.CUSTOMIZATION_3D_HOTSPOT_ACTIONS;
|
|
if (!all || typeof all !== 'object') return null;
|
|
return all[detail.id] || all[detail.name] || null;
|
|
};
|
|
|
|
const handle3DHotspotClick = (event) => {
|
|
const detail = event?.detail || {};
|
|
const viewer = get3DViewer();
|
|
if (!viewer) return;
|
|
|
|
if (typeof window.CUSTOMIZATION_3D_ON_HOTSPOT_CLICK === 'function') {
|
|
try {
|
|
window.CUSTOMIZATION_3D_ON_HOTSPOT_CLICK(detail, viewer);
|
|
} catch (err) {
|
|
console.warn('[Customization] CUSTOMIZATION_3D_ON_HOTSPOT_CLICK failed:', err);
|
|
}
|
|
}
|
|
|
|
const action = getHotspotActionConfig(detail);
|
|
if (action?.door) {
|
|
viewer.door?.toggle(action.door);
|
|
}
|
|
if (action?.clipping) {
|
|
const clip = action.clipping;
|
|
if (typeof clip.height === 'number') {
|
|
viewer.clipping?.setY(
|
|
clip.height,
|
|
clip.keepBelow !== false,
|
|
Array.isArray(clip.meshNames) ? clip.meshNames : []
|
|
);
|
|
}
|
|
}
|
|
};
|
|
|
|
const setup3DEventBridge = () => {
|
|
if (document.documentElement.dataset.customization3dEventsBoundCopy === '1') return;
|
|
document.documentElement.dataset.customization3dEventsBoundCopy = '1';
|
|
|
|
let hotspotRenderTimer = null;
|
|
const renderConfiguredHotspots = () => {
|
|
if (hotspotRenderTimer) clearTimeout(hotspotRenderTimer);
|
|
hotspotRenderTimer = setTimeout(() => {
|
|
hotspotRenderTimer = null;
|
|
const hotspots = get3DHotspots();
|
|
const viewer = get3DViewer();
|
|
viewer?.hotspot?.clear?.();
|
|
if (!hotspots.length) return;
|
|
viewer?.hotspot?.render(hotspots);
|
|
}, 200);
|
|
};
|
|
|
|
document.addEventListener('3d:scene:ready', renderConfiguredHotspots);
|
|
document.addEventListener('3d:hotspots:update', renderConfiguredHotspots);
|
|
document.addEventListener('3d:hotspot:click', handle3DHotspotClick);
|
|
};
|
|
|
|
const setupWheelScrollLockOn3DContainer = () => {
|
|
const container = document.querySelector('[data-3d-container]');
|
|
if (!container || container.dataset.customization3dWheelLockCopy === '1') return;
|
|
container.dataset.customization3dWheelLockCopy = '1';
|
|
container.addEventListener(
|
|
'wheel',
|
|
(e) => {
|
|
if (!container.contains(e.target)) return;
|
|
e.preventDefault();
|
|
},
|
|
{ capture: true, passive: false }
|
|
);
|
|
};
|
|
|
|
const refreshHotspots = () => {
|
|
document.dispatchEvent(new CustomEvent('3d:hotspots:update', { bubbles: true }));
|
|
};
|
|
|
|
window.Customization3DInteractions = {
|
|
load3DModel,
|
|
clear3DModel,
|
|
show3DEmpty,
|
|
get3DModelUrl,
|
|
get3DHotspots,
|
|
refreshHotspots,
|
|
};
|
|
|
|
const init3DInteractions = () => {
|
|
setup3DEventBridge();
|
|
setupWheelScrollLockOn3DContainer();
|
|
};
|
|
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', init3DInteractions);
|
|
} else {
|
|
init3DInteractions();
|
|
}
|
|
})();
|