Files
zhengte.babylonjs-sdk/index.js
2026-06-04 19:01:53 +08:00

454 lines
15 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 },
camera: {
position: { x: 5, y: 2, z: 7 }, // 相机位置x-左右y-上下z-前后
target: { x: 0, y: 1, z: 0 } // 相机目标点:相机看向的位置
},
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 无20
const only10 = /(?=.*10)(?!.*13)(?!.*12)(?!.*20)/.test(pergolaSku)
// 同时包含10和12
const only10_12 = /(?=.*10)(?=.*12)/.test(pergolaSku)
// 同时包含10和20
const only10_20 = /(?=.*10)(?=.*20)/.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('左', '右')
}
//棚子同时包10和20的并且含配件是10
if (only10_20 && has10) {
if (pergolaSku === "SPF111S1020PILLAR4PCS") {
division_include.push('左', '右')
}
else {
division_include.push('前', '后', '左', '右', "前1", "后1", "前2", "后2")
}
}
//棚子同时包10和20的并且含配件是13
if (only10_20 && has13) {
if (pergolaSku === "SPF111S1020PILLAR4PCS") {
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);
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,
snapToZone: true, // 拖拽吸附到最近的分割区域
returnWhenOutOfBounds: true, // 拖拽到区域外时返回原位置
handleOccupiedZone: true, // 处理已占用区域false=允许重叠)
occupiedZoneAction: 'replace' // 当开关3=true时的行为'return'=返回原位置,'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,
metallic: +metallic,
roughness: +roughness,
});
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);
}
}