428 lines
14 KiB
JavaScript
428 lines
14 KiB
JavaScript
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);
|
||
}
|
||
} |