import { EXRCubeTexture } from '@babylonjs/core'; import apiConfig from './src/config.js'; import { setSkuMapping, getSkuByModelId, clearSkuMapping, clearAllSkuMappings } from './src/skuMapping.js'; // 存储 kernel 实例 let kernelInstance = null; // 导出 SKU 映射相关函数,方便外部使用 export { getSkuByModelId, clearSkuMapping, clearAllSkuMappings }; /** * 初始化应用逻辑 - 注入 kernel 实例 * @param {Object} kernel - SDK kernel 实例 * @returns {Object} kernel 实例 */ export const initApp = (kernel) => { if (!kernel) { throw new Error('kernel 实例是必需的'); } kernelInstance = kernel; console.log('应用逻辑已初始化,kernel 实例已注入'); return kernelInstance; }; //全局唯一棚子sku let pergolaSku = "" /** * 获取当前 kernel 实例 */ const getKernel = () => { if (!kernelInstance) { throw new Error('请先调用 initApp(kernel) 初始化 kernel 实例'); } return kernelInstance; }; //初始化 export const init = async (customConfig = {}) => { const kernel = getKernel(); const defaultConfig = { container: document.querySelector('#renderDom'), modelUrlList: [], env: { envPath: 'https://cdn.files.zguiy.com/zt/environment.env', intensity: 1.2, rotationY: 0.3, background: false }, gizmo: { position: false, rotation: false, scale: false }, outline: { enable: true, color: "#2196F3", thickness: 1, occlusionStrength: 0.1, occlusionThreshold: 0.0002 } }; // 合并用户自定义配置 const config = { ...defaultConfig, ...customConfig }; kernel.init(config); } //初始化加载模型 export const getAutoLoadModelList = async () => { const kernel = getKernel(); const url = apiConfig.getApiUrl('/api/models/auto-load/list') const response = await fetch(url) const data = await response.json() const models = data.data // 这就是模型列表 models.forEach(model => { if (model.placement_zone) { const { alpha, border_color, color, show_border, thickness, walls } = model.placement_zone kernel.dropZone.setData({ color: color, alpha: +alpha, thickness: thickness, showBorder: !show_border, borderColor: border_color, walls: walls }); } kernel.model.add({ modelName: model.name + '_' + model.category, modelId: model.category, modelUrl: model.file_url, modelControlType: model.model_control_type, }); }) } //获取放置区域 export const getPlacementZone = async (sku) => { //pergolaSku 是需要在加载棚子的时取其引用,传进来的sku则是配件的sku,根据配件的sku来判断放置区域 const kernel = getKernel(); let division_include = [] // 同时包含10和13 const only10_13 = /(?=.*10)(?=.*13)/.test(pergolaSku) // 只包含10 无13 无12 const only10 = /(?=.*10)(?!.*13)(?!.*12)/.test(pergolaSku) // 同时包含10和12 const only10_12 = /(?=.*10)(?=.*12)/.test(pergolaSku) // 1. 只要字符串里包含 10,就返回 true const has10 = /10/.test(sku); // 2. 只要字符串里包含 13,就返回 true const has13 = /13/.test(sku); // 2. 只要字符串里包含 12,就返回 true const has12 = /12/.test(sku); //棚子包含10,不包含13 并且配件是10 说明是正方体 或者是10*20的 if (only10 && has10) { division_include.push('前', '后', '左', '右', "前1", "后1", "前2", "后2") } //棚子同时包10和13的并且含配件是10 if (only10_13 && has10) { division_include.push('左', '右') } //棚子同时包10和13的并且含配件是13 if (only10_13 && has13) { division_include.push('前', '后') } //棚子同时包10和12的并且含配件是12 if (only10_12 && has12) { division_include.push('前', '后') } //棚子同时包10和12的并且含配件是10 if (only10_12 && has10) { division_include.push('左', '右') } const response = await fetch(apiConfig.getApiUrl(`/api/product-configs/by-sku/${sku}`)); const result = await response.json(); if (result.code === 200) { // await initPlacementZoneConfig(); const { enable_placement_zone, wall_divisions } = result.data; // const {position_x, position_y, position_z} = data; if (enable_placement_zone && wall_divisions != undefined) { console.log(wall_divisions); const filteredDivisions = wall_divisions.filter(item => division_include.includes(item.name)) console.log(filteredDivisions); // 只清除旧的放置区域网格,不清除模型 kernel.dropZone.clearZones(); const divisions = filteredDivisions.map(wall => ({ name: wall.name, divisions: wall.divisions })) kernel.dropZone.updateDivisions(divisions); // 显示放置区域 kernel.dropZone.show(); } } } //执行事件 export const getEvent = async (dropzone_data, sku) => { // 将模型放置到该区域 try { const response = await fetch(apiConfig.getApiUrl(`/api/product-configs/by-sku/${sku}`)); const result = await response.json(); if (result.code === 200 && result.data) { console.log('SKU配置数据:', result.data); console.log('关联事件:', result.data.events); // 使用 for...of 循环以支持 await await executeEvent(dropzone_data, result, sku) } else { console.log(`未查询到数据`); } } catch (error) { console.error(`查询SKU配置或替换模型失败:`, error); } } //点击放置区域执行事件 一般是换配件 export const executeEvent = async (dropzone_data, result, sku) => { const kernel = getKernel(); const { wallName, index, transform } = dropzone_data; const { position, rotation } = transform; let modelId = null; // 在外部声明,用于在两个循环之间传递 let modelName = null; let pergolaSku = null; // 用于存储棚子的 SKU // 第一次循环:处理 change_model for (const event of result.data.events) { if (event.event_type === 'change_model') { const { name, file_url, model_control_type, category } = event.target_data; // 生成唯一的模型ID modelId = Date.now(); modelName = name; kernel.dropZone.recordModelPlacement(wallName, index, name + '_' + modelId); // 记录模型ID到SKU的映射 setSkuMapping(modelId, sku); await kernel.model.add({ modelName: name, modelId: modelId, modelUrl: file_url, modelControlType: model_control_type, drag: { enable: true, axis: rotation.y === 0 || rotation.y === 180 ? 'x' : 'z', step: 0.1, boundaryConstraint: true, // 启用边界限制 snapToZone: true, // 启用拖拽吸附到最近的分割区域 // 拖拽到已占用区域时的行为:'return' 返回原位置,'replace' 替换目标位置的模型(默认 'return') onOccupiedZone: 'replace' }, transform: { position: position, rotation: rotation, } }); console.log(`百叶模型已放置为 ${name + '_' + modelId}`); } } // 第二次循环:处理 change_color(此时模型已加载完成) for (const event of result.data.events) { if (event.event_type === 'change_color') { const materialName = event.material_name; const { color, color_map_url, normal_map_url, metallic, roughness } = event.target_data; kernel.material.apply({ target: materialName, modelId: modelName + '_' + modelId, // 传入 modelId,只替换该模型的材质 albedoColor: color, albedoTexture: color_map_url, normalMap: normal_map_url, metallic: +metallic, roughness: +roughness }); console.log(`百叶模型颜色已替换为 ${color}`); } } // 查找棚子的 SKU(从已加载的模型中查找 model_control_type 为 'pergola' 的模型) const allModels = kernel.model.getAllMetadata(); for (const model of allModels) { if (model.modelControlType === 'pergola') { pergolaSku = getSkuByModelId(model.modelId); if (pergolaSku) { break; } } } console.log('当前棚子的 SKU:', pergolaSku); return pergolaSku; } //一般是换棚子/换颜色/设置放置区域 export const executeEvent2 = async (result, sku) => { const kernel = getKernel(); // 检查是否有模型更换事件 const hasModelChange = result.data.events.some(e => e.event_type === 'change_model'); // 检查新模型是否已经存在 let modelAlreadyExists = false; if (hasModelChange) { const firstModelEvent = result.data.events.find(e => e.event_type === 'change_model'); if (firstModelEvent && firstModelEvent.target_data) { const { name, category } = firstModelEvent.target_data; modelAlreadyExists = kernel.model.exists(name + '_' + category); console.log(`检查模型 ${name + '_' + category} 是否存在:`, modelAlreadyExists); } } kernel.dropZone.hide(); // 只有在需要更换模型且模型不存在时才清除 if (hasModelChange && !modelAlreadyExists) { console.log('模型不存在,执行清除操作'); kernel.model.removeAll(); // 清除所有 SKU 映射 clearAllSkuMappings(); } // 先处理所有 change_model 事件 for (const event of result.data.events) { console.log(event); if (event.event_type === 'change_model') { const { target_data } = event; console.log(event.target_data); if (!target_data) { console.error('change_model事件缺少target_data') return; }; const { id, name, file_url, model_control_type, category, placement_zone } = target_data; // 如果模型已存在,跳过加载 if (modelAlreadyExists) { console.log(`模型 ${name + '_' + category} 已存在,跳过加载`); continue; } if (placement_zone) { const { alpha, border_color, color, show_border, thickness, walls } = placement_zone kernel.dropZone.setData({ color: color, alpha: +alpha, thickness: thickness, showBorder: !show_border, borderColor: border_color, walls: walls }); } // 记录模型ID到SKU的映射 setSkuMapping(category, sku); // 加载并放置模型(使用 category 作为 modelId) await kernel.model.add({ modelName: name, modelId: category, modelUrl: file_url, modelControlType: model_control_type, }) console.log(`模型已放置为 ${name + '_' + category}`); } } // 等待模型加载完成后,再处理 change_color 事件 for (const event of result.data.events) { if (event.event_type === 'change_color') { const materialName = event.material_name; const { color, color_map_url, normal_map_url, metallic, roughness } = event.target_data; console.log('替换模型颜色:', event.target_data); kernel.material.apply({ target: materialName, albedoColor: color, albedoTexture: color_map_url, normalMap: normal_map_url, }); console.log(`百叶模型颜色已替换为 ${color}`); } } } //加载热点 export const getHotspot = async () => { const kernel = getKernel(); try { // 从后端获取激活状态的热点列表 const response = await fetch(apiConfig.getApiUrl('/api/hotspots?status=active&page=1&pageSize=100')); const result = await response.json(); if (result.code === 200 && result.data.list.length > 0) { // 将后端数据转换为 SDK 需要的格式 const hotspots = result.data.list.map(item => ({ id: item.id, type: 'hotspot', name: item.name, meshName: item.name, // 可以根据实际情况调整 icon: item.image_url, position: [item.position_x, item.position_y, item.position_z], radius: item.radius, color: "#000000", payload: { skus: item.skus || [], }, })); // 渲染热点 kernel.hotspot.render(hotspots); console.log('热点渲染成功:', hotspots); } else { console.log('没有可用的热点数据'); } } catch (error) { console.error('获取热点数据失败:', error); } } //点击右侧按钮自动判断 export const getProductConfig = async (sku) => { try { const response = await fetch(`${apiConfig.getApiUrl(`/api/product-configs/by-sku/${sku}`)}`); const result = await response.json(); if (result.code === 200) { console.log(result.data); const { enable_placement_zone } = result.data; //如果触发的是配件,需要显示放置区域 if (enable_placement_zone) { if (pergolaSku === "") { console.error("请先加载棚子模型") return; } getPlacementZone(sku) } //如果触发的是换棚子模型 else { pergolaSku = sku; executeEvent2(result, sku) } } } catch (error) { console.error('获取产品配置失败:', error); } }