This commit is contained in:
2026-05-29 14:24:59 +08:00
parent a8ae4ffc57
commit 48456acd3d
7 changed files with 755 additions and 115 deletions

View File

@ -181,44 +181,48 @@ const getEvent = async (dropzone_data, sku) => {
//点击放置区域执行事件 一般是换配件
const executeEvent = async (dropzone_data, result, sku) => {
const kernel = getKernel();
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;
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,
},
transform: {
position: position,
rotation: rotation,
}
});
console.log(`百叶模型已放置为 ${name + '_' + modelId}`);
}
}
// 生成唯一的模型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) {
@ -265,7 +269,7 @@ const isModelExists = (modelId) => {
}
//一般是换棚子/换颜色/设置放置区域
const executeEvent2 = async (result, sku) => {
const executeEvent2 = async (result, sku) => {
const kernel = getKernel();
// 检查是否有模型更换事件
@ -418,8 +422,8 @@ const getProductConfig = async (sku) => {
}
// API 配置
const API_BASE_URL = 'https://ztserver.zguiy.com';
//const API_BASE_URL = 'https://ztserver.zguiy.com';
const API_BASE_URL = 'http://localhost:26517';
const getApiUrl = (path) => {
return `${API_BASE_URL}${path}`;
};

View File

@ -453,7 +453,7 @@
<div class="category-content expanded">
<div class="option-group">
<button class="option-btn" data-option="color-1">SPFPDS13FTW</button>
<button class="option-btn" data-option="color-2">SPFPDS13FTC</button>
<button class="option-btn" data-option="color-2">SPFSW13FTC</button>
</div>
</div>
</div>
@ -870,11 +870,7 @@
console.log(kernel);
// 监听放置区域点击事件
kernel.on('dropzone:click', function (dropzone_data) {
window.AppLogic.getEvent(dropzone_data, sku);
});
// 存储当前选中的材质名和网格
var currentMaterialName = '';
var currentPickedMesh = null;

View File

@ -221,33 +221,40 @@
cursor: pointer;
}
/* 进度条样式 */
/* 加载遮罩样式 */
#progress-container {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 80%;
max-width: 500px;
background: rgba(255, 255, 255, 0.2);
border-radius: 10px;
padding: 10px;
z-index: 1000;
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background: rgba(0, 0, 0, 0.7);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 9999;
}
#progress-bar {
width: 0%;
height: 10px;
background: linear-gradient(90deg, #4CAF50, #45a049);
border-radius: 5px;
transition: width 0.1s ease;
.spinner {
width: 50px;
height: 50px;
border: 4px solid rgba(255, 255, 255, 0.3);
border-top-color: #4CAF50;
border-radius: 50%;
animation: spin 1s linear infinite;
}
#progress-text {
color: white;
text-align: center;
margin-top: 5px;
font-size: 14px;
@keyframes spin {
to {
transform: rotate(360deg);
}
}
.loading-text {
color: #fff;
font-size: 16px;
margin-top: 20px;
}
</style>
</head>
@ -258,8 +265,8 @@
<div id="canvas-container">
<canvas id="renderDom"></canvas>
<div id="progress-container" style="display: none;">
<div id="progress-bar"></div>
<div id="progress-text">0%</div>
<div class="spinner"></div>
<div class="loading-text">加载中...</div>
</div>
<!-- 生成放置区域按钮 -->
@ -847,9 +854,25 @@
// 监听放置区域点击事件
kernel.on('dropzone:click', async (dropzone_data) => {
// 显示进度条
const progressContainer = document.getElementById('progress-container');
if (progressContainer) {
progressContainer.style.display = 'flex';
}
getEvent(dropzone_data, sku)
});
// 监听模型加载完成事件
kernel.on('model:loaded', (data) => {
console.log('模型加载完成', data);
// 隐藏进度条
const progressContainer = document.getElementById('progress-container');
if (progressContainer) {
progressContainer.style.display = 'none';
}
});

View File

@ -79877,10 +79877,8 @@ discard;}}
}
/** 初始化灯光并开启阴影 */
Awake() {
new DirectionalLight(
"mainLight",
new Vector3(0, -0.5, -1)
);
const light = new DirectionalLight("sun", new Vector3(-1, -2, -1), this.mainApp.appScene.object);
light.intensity = 1.2;
}
}
@ -354748,6 +354746,22 @@ clipPos=viewProjection*worldPos;previousClipPos=previousViewProjection*previousW
}
if (drag) {
this.mainApp.appModelDrag?.configureDrag(modelName + "_" + modelId, drag);
if (this.mainApp.appDropZone) {
let modelWallName = null;
const fullModelId = modelName + "_" + modelId;
this.mainApp.appDropZone["zoneModelMap"]?.forEach((id, zoneKey) => {
if (id === fullModelId) {
const match = zoneKey.match(/^(.+)\[(\d+)\]$/);
if (match) {
modelWallName = match[1];
}
}
});
if (modelWallName && this.mainApp.appDropZone.isWallFull(modelWallName)) {
console.log(`[拖拽控制] 墙面 ${modelWallName} 已满,禁用模型 ${fullModelId} 的拖拽`);
this.mainApp.appModelDrag?.setDragEnabled(fullModelId, false);
}
}
}
this.mainApp.gameManager?.updateDictionaries();
EventBridge.modelLoaded({ urls: [modelUrl] });
@ -354907,6 +354921,9 @@ clipPos=viewProjection*worldPos;previousClipPos=previousViewProjection*previousW
this.modelDic.Remove(modelName);
this.modelMetadataDic.Remove(modelName);
this.mainApp.gameManager?.updateDictionaries();
if (this.mainApp.appDropZone && typeof this.mainApp.appDropZone.notifyModelRemoved === "function") {
this.mainApp.appDropZone.notifyModelRemoved(modelName);
}
return true;
}
/**
@ -355067,9 +355084,10 @@ clipPos=viewProjection*worldPos;previousClipPos=previousViewProjection*previousW
* 将模型放置到指定的放置区域
* @param modelId 模型ID
* @param zoneInfo 放置区域信息
* @param offsetDistance 距离墙面的偏移距离默认0.1正数向外
* @param offsetDistance 距离墙面的偏移距离默认0正数向外
*/
placeToZone(modelId, zoneInfo, offsetDistance = 0) {
console.log(zoneInfo);
const meshes = this.modelDic.Get(modelId);
if (!meshes?.length) {
console.warn(`Model not found: ${modelId}`);
@ -355119,6 +355137,12 @@ clipPos=viewProjection*worldPos;previousClipPos=previousViewProjection*previousW
highlightLayer = null;
originalMaterial = null;
highlightedMesh = null;
pointerDownTime = 0;
pointerDownPickInfo = null;
longPressTimer = null;
longPressThreshold = 500;
// 长按阈值(毫秒)
isLongPress = false;
constructor(mainApp) {
super(mainApp);
}
@ -355143,29 +355167,85 @@ clipPos=viewProjection*worldPos;previousClipPos=previousViewProjection*previousW
}
if (type === PointerEventTypes.POINTERDOWN) {
this.oldPoint.set(pointerEvent.clientX, 0, pointerEvent.clientY);
this.pointerDownTime = Date.now();
this.pointerDownPickInfo = pickInfo;
this.isLongPress = false;
if (this.longPressTimer) {
clearTimeout(this.longPressTimer);
}
this.longPressTimer = setTimeout(() => {
this.isLongPress = true;
this.handleLongPress(pointerEvent, this.pointerDownPickInfo);
}, this.longPressThreshold);
} else if (type === PointerEventTypes.POINTERUP) {
if (this.longPressTimer) {
clearTimeout(this.longPressTimer);
this.longPressTimer = null;
}
this.newPoint.set(pointerEvent.clientX, 0, pointerEvent.clientY);
const distance = Vector3.Distance(this.oldPoint, this.newPoint);
if (distance < 5) {
if (this.isLongPress) {
console.log("[长按] 松手,隐藏分割区域");
this.mainApp.appDropZone.hide();
}
if (distance < 5 && !this.isLongPress) {
this.handleSingleClick(pointerEvent, pickInfo);
}
this.isLongPress = false;
} else if (type === PointerEventTypes.POINTERMOVE) {
this.newPoint.set(pointerEvent.clientX, 0, pointerEvent.clientY);
const distance = Vector3.Distance(this.oldPoint, this.newPoint);
if (distance > 5 && this.longPressTimer) {
clearTimeout(this.longPressTimer);
this.longPressTimer = null;
}
}
});
}
// 处理长按
handleLongPress(evt, pickInfo) {
if (pickInfo && pickInfo.hit && pickInfo.pickedMesh) {
if (pickInfo.pickedMesh.metadata?.type === "hotspot") {
return;
}
if (pickInfo.pickedMesh.name.startsWith("placement_")) {
return;
}
const modelName = this.mainApp.appModel.findModelNameByMesh(pickInfo.pickedMesh);
if (modelName) {
const wallName = this.findModelWallName(modelName);
if (wallName) {
console.log(`[长按] 模型 ${modelName} 位于墙面 ${wallName},显示该墙面的放置区域`);
this.mainApp.appDropZone.showWall(wallName);
}
}
}
}
// 查找模型所在的墙面
findModelWallName(modelId) {
const zoneModelMap = this.mainApp.appDropZone["zoneModelMap"];
if (!zoneModelMap) return null;
for (const [zoneKey, id] of zoneModelMap.entries()) {
if (id === modelId) {
const match = zoneKey.match(/^(.+)\[(\d+)\]$/);
if (match) {
return match[1];
}
}
}
return null;
}
// 处理单击
handleSingleClick(evt, pickInfo) {
if (pickInfo && pickInfo.hit && pickInfo.pickedMesh && pickInfo.pickedPoint) {
if (pickInfo.pickedMesh.metadata?.type === "hotspot") {
return;
}
if (pickInfo.pickedMesh.name === "gridGround" || pickInfo.pickedMesh.name === "ground" || pickInfo.pickedMesh.name === "sphere_yundong_mesh") {
return;
}
if (pickInfo.pickedMesh.name.startsWith("placement_")) {
const zones = this.mainApp.appDropZone.getPlacementZones();
const clickedZone = zones.find((zone) => zone.mesh === pickInfo.pickedMesh);
if (clickedZone) {
const offsetDistance = 0.05;
const offsetDistance = 0;
const targetPosition = clickedZone.center.add(clickedZone.normal.scale(offsetDistance));
const targetDirection = clickedZone.normal.scale(-1);
const angle = Math.atan2(targetDirection.x, targetDirection.z);
@ -355186,7 +355266,6 @@ clipPos=viewProjection*worldPos;previousClipPos=previousViewProjection*previousW
rotation: {
x: 0,
y: angle * 180 / Math.PI,
// 转换为角度
z: 0
},
scale: {
@ -355200,6 +355279,7 @@ clipPos=viewProjection*worldPos;previousClipPos=previousViewProjection*previousW
}
}
this.mainApp.appDomTo3D.hideAll();
this.mainApp.appDropZone.hide();
const materialName = pickInfo.pickedMesh.material?.name || "";
const holdingShift = Boolean(evt.shiftKey);
const modelMeshes = this.mainApp.appModel.getModelMeshesByMesh(pickInfo.pickedMesh);
@ -355222,7 +355302,6 @@ clipPos=viewProjection*worldPos;previousClipPos=previousViewProjection*previousW
modelControlType: modelMetadata?.modelControlType
});
} else {
console.log(1111);
this.mainApp.appSelectionOutline.clear();
this.mainApp.appPositionGizmo.detach();
this.mainApp.appDomTo3D.hideAll();
@ -356535,11 +356614,44 @@ clipPos=viewProjection*worldPos;previousClipPos=previousViewProjection*previousW
}
const pointerDragBehavior = new PointerDragBehavior({ dragAxis });
pointerDragBehavior.useObjectOrientationForDragging = false;
let dragStartPosition = null;
let hasShownZones = false;
pointerDragBehavior.onDragStartObservable.add(() => {
const meshes = this.mainApp.appModel?.modelDic?.Get(modelId);
if (meshes && meshes.length > 0) {
dragStartPosition = meshes[0].position.clone();
}
this.disableCameraControl();
hasShownZones = false;
});
pointerDragBehavior.onDragObservable.add((event) => {
const meshes = this.mainApp.appModel?.modelDic?.Get(modelId);
if (meshes && meshes.length > 0 && dragStartPosition) {
const distance = Vector3.Distance(dragStartPosition, meshes[0].position);
if (distance > 0.01 && !hasShownZones && dragInfo.config.snapToZone) {
this.showZonesForModel(modelId);
hasShownZones = true;
}
}
});
pointerDragBehavior.onDragEndObservable.add(() => {
const meshes = this.mainApp.appModel?.modelDic?.Get(modelId);
let hasMoved = false;
if (meshes && meshes.length > 0 && dragStartPosition) {
const distance = Vector3.Distance(dragStartPosition, meshes[0].position);
hasMoved = distance > 0.01;
}
this.enableCameraControl();
if (hasMoved) {
if (dragInfo.config.snapToZone && hasShownZones) {
this.hideZonesForModel(modelId);
this.snapModelToZone(modelId);
} else {
this.updateModelZoneMapping(modelId);
}
}
dragStartPosition = null;
hasShownZones = false;
});
return pointerDragBehavior;
}
@ -356632,6 +356744,259 @@ clipPos=viewProjection*worldPos;previousClipPos=previousViewProjection*previousW
camera.attachControl(canvas, true);
}
}
/**
* 显示模型所在墙面的分割区域
* @param modelId 模型ID
*/
showZonesForModel(modelId) {
const appDropZone = this.mainApp.appDropZone;
if (!appDropZone) return;
let wallName = null;
appDropZone["zoneModelMap"]?.forEach((id, zoneKey) => {
if (id === modelId) {
const match = zoneKey.match(/^(.+)\[(\d+)\]$/);
if (match) {
wallName = match[1];
}
}
});
if (wallName) {
console.log(`[拖拽吸附] 显示墙面 ${wallName} 的分割区域`);
appDropZone.showWall(wallName);
}
}
/**
* 隐藏分割区域
* @param modelId 模型ID
*/
hideZonesForModel(modelId) {
const appDropZone = this.mainApp.appDropZone;
if (!appDropZone) return;
console.log(`[拖拽吸附] 隐藏分割区域`);
appDropZone.hide();
}
/**
* 将模型吸附到最近的分割区域
* @param modelId 模型ID
*/
snapModelToZone(modelId) {
const meshes = this.mainApp.appModel?.modelDic?.Get(modelId);
if (!meshes || !meshes.length) return;
const rootMesh = meshes[0];
const appDropZone = this.mainApp.appDropZone;
if (!appDropZone) return;
let wallName = null;
let originalZoneIndex = -1;
appDropZone["zoneModelMap"]?.forEach((id, zoneKey) => {
if (id === modelId) {
const match = zoneKey.match(/^(.+)\[(\d+)\]$/);
if (match) {
wallName = match[1];
originalZoneIndex = parseInt(match[2]);
}
}
});
if (!wallName) return;
const wallZones = appDropZone.getZonesByWall(wallName);
if (!wallZones.length) return;
let closestZoneIndex = -1;
let minDistance = Number.POSITIVE_INFINITY;
wallZones.forEach((zone, index) => {
const distance = rootMesh.position.subtract(zone.center).length();
if (distance < minDistance) {
minDistance = distance;
closestZoneIndex = index;
}
});
if (closestZoneIndex === -1) return;
const dragInfo = this.modelDragMap.get(modelId);
const returnWhenOutOfBounds = dragInfo?.config.returnWhenOutOfBounds ?? false;
const handleOccupiedZone = dragInfo?.config.handleOccupiedZone ?? false;
const occupiedZoneAction = dragInfo?.config.occupiedZoneAction ?? "return";
let isOutOfBounds = false;
let minX = Number.POSITIVE_INFINITY;
let maxX = Number.NEGATIVE_INFINITY;
let minZ = Number.POSITIVE_INFINITY;
let maxZ = Number.NEGATIVE_INFINITY;
wallZones.forEach((zone) => {
const halfWidth = zone.width / 2;
if (Math.abs(zone.normal.x) > 0.5) {
minZ = Math.min(minZ, zone.center.z - halfWidth);
maxZ = Math.max(maxZ, zone.center.z + halfWidth);
} else if (Math.abs(zone.normal.z) > 0.5) {
minX = Math.min(minX, zone.center.x - halfWidth);
maxX = Math.max(maxX, zone.center.x + halfWidth);
}
});
const currentPos = rootMesh.position;
if (minX !== Number.POSITIVE_INFINITY && maxX !== Number.NEGATIVE_INFINITY) {
if (currentPos.x < minX || currentPos.x > maxX) {
isOutOfBounds = true;
}
}
if (minZ !== Number.POSITIVE_INFINITY && maxZ !== Number.NEGATIVE_INFINITY) {
if (currentPos.z < minZ || currentPos.z > maxZ) {
isOutOfBounds = true;
}
}
if (isOutOfBounds) {
console.log(`[拖拽吸附] 模型 ${modelId} 超出边界`);
if (returnWhenOutOfBounds) {
console.log(`[拖拽吸附] 启用边界返回,回到原区域 ${originalZoneIndex}`);
if (originalZoneIndex !== -1) {
const originalZone = wallZones[originalZoneIndex];
if (originalZone) {
const offsetDistance2 = -0.05;
const returnPosition = originalZone.center.add(originalZone.normal.scale(offsetDistance2));
rootMesh.position.copyFrom(returnPosition);
const targetDirection2 = originalZone.normal.scale(-1);
const angle2 = Math.atan2(targetDirection2.x, targetDirection2.z);
rootMesh.rotation.y = angle2;
console.log(`[拖拽吸附] 模型 ${modelId} 已返回原区域 ${originalZoneIndex}`);
return;
}
}
} else {
console.log(`[拖拽吸附] 未启用边界返回,保持当前位置,不做吸附`);
this.updateModelZoneMapping(modelId);
return;
}
}
const targetZone = wallZones[closestZoneIndex];
const targetZoneKey = `${wallName}[${closestZoneIndex}]`;
const occupyingModelId = appDropZone["zoneModelMap"]?.get(targetZoneKey);
if (occupyingModelId && occupyingModelId !== modelId) {
console.log(`[拖拽吸附] 目标区域 ${closestZoneIndex} 已被模型 ${occupyingModelId} 占用`);
if (handleOccupiedZone) {
if (occupiedZoneAction === "return") {
console.log(`[拖拽吸附] 配置为返回原位置,回到区域 ${originalZoneIndex}`);
if (originalZoneIndex !== -1) {
const originalZone = wallZones[originalZoneIndex];
if (originalZone) {
const offsetDistance2 = -0.05;
const returnPosition = originalZone.center.add(originalZone.normal.scale(offsetDistance2));
rootMesh.position.copyFrom(returnPosition);
const targetDirection2 = originalZone.normal.scale(-1);
const angle2 = Math.atan2(targetDirection2.x, targetDirection2.z);
rootMesh.rotation.y = angle2;
console.log(`[拖拽吸附] 模型 ${modelId} 返回原区域 ${originalZoneIndex}`);
return;
}
}
} else if (occupiedZoneAction === "replace") {
console.log(`[拖拽吸附] 配置为替换模型,将替换模型 ${occupyingModelId}`);
}
} else {
console.log(`[拖拽吸附] 未启用占用区域处理,允许重叠`);
}
}
const offsetDistance = 0;
const snapPosition = targetZone.center.add(targetZone.normal.scale(offsetDistance));
rootMesh.position.copyFrom(snapPosition);
const targetDirection = targetZone.normal.scale(-1);
const angle = Math.atan2(targetDirection.x, targetDirection.z);
rootMesh.rotation.y = angle;
console.log(`[拖拽吸附] 模型 ${modelId} 吸附到区域 ${closestZoneIndex}`);
this.updateModelZoneMapping(modelId);
}
/**
* 更新模型所属的分割区域映射
* @param modelId 模型ID
*/
updateModelZoneMapping(modelId) {
const meshes = this.mainApp.appModel?.modelDic?.Get(modelId);
if (!meshes || !meshes.length) return;
const rootMesh = meshes[0];
const modelPosition = rootMesh.position;
console.log(`[边界检测] 模型 ${modelId} 拖拽结束,当前位置:`, modelPosition);
const appDropZone = this.mainApp.appDropZone;
if (!appDropZone) return;
let originalWallName = null;
appDropZone["zoneModelMap"]?.forEach((id, zoneKey) => {
if (id === modelId) {
const match = zoneKey.match(/^(.+)\[(\d+)\]$/);
if (match) {
originalWallName = match[1];
}
}
});
if (!originalWallName) {
console.log(`[边界检测] 模型 ${modelId} 未找到原始墙面,跳过检测`);
return;
}
console.log(`[边界检测] 模型 ${modelId} 原始墙面: ${originalWallName}`);
const wallZones = appDropZone.getZonesByWall(originalWallName);
if (!wallZones.length) return;
console.log(`[边界检测] 墙面 ${originalWallName}${wallZones.length} 个分割区域`);
let closestZoneIndex = -1;
let minDistance = Number.POSITIVE_INFINITY;
wallZones.forEach((zone, index) => {
const distance = modelPosition.subtract(zone.center).length();
console.log(`[边界检测] 区域 ${index} 中心:`, zone.center, `距离: ${distance.toFixed(3)}`);
if (distance < minDistance) {
minDistance = distance;
closestZoneIndex = index;
}
});
if (closestZoneIndex === -1) {
console.log(`[边界检测] 未找到最近的区域`);
return;
}
console.log(`[边界检测] 模型 ${modelId} 最接近区域 ${closestZoneIndex},距离: ${minDistance.toFixed(3)}`);
let currentZoneIndex = -1;
appDropZone["zoneModelMap"]?.forEach((id, zoneKey) => {
if (id === modelId) {
const match = zoneKey.match(/^.+\[(\d+)\]$/);
if (match) {
currentZoneIndex = parseInt(match[1]);
}
}
});
if (currentZoneIndex !== closestZoneIndex) {
console.log(`[边界检测] 模型 ${modelId} 从区域 ${currentZoneIndex} 移动到区域 ${closestZoneIndex}`);
if (currentZoneIndex !== -1) {
const oldKey = `${originalWallName}[${currentZoneIndex}]`;
appDropZone["zoneModelMap"]?.delete(oldKey);
console.log(`[边界检测] 删除旧映射: ${oldKey}`);
}
const newKey = `${originalWallName}[${closestZoneIndex}]`;
const existingModelId = appDropZone["zoneModelMap"]?.get(newKey);
const dragInfo = this.modelDragMap.get(modelId);
const handleOccupiedZone = dragInfo?.config.handleOccupiedZone ?? false;
const occupiedZoneAction = dragInfo?.config.occupiedZoneAction ?? "return";
if (existingModelId && existingModelId !== modelId) {
console.log(`[边界检测] 目标区域 ${closestZoneIndex} 已有模型 ${existingModelId}`);
if (handleOccupiedZone && occupiedZoneAction === "replace") {
console.log(`[边界检测] 配置为替换模式,交换位置`);
if (currentZoneIndex !== -1) {
const swapKey = `${originalWallName}[${currentZoneIndex}]`;
appDropZone["zoneModelMap"]?.set(swapKey, existingModelId);
console.log(`[边界检测] 模型 ${existingModelId} 移动到区域 ${currentZoneIndex}`);
const existingMeshes = this.mainApp.appModel?.modelDic?.Get(existingModelId);
if (existingMeshes && existingMeshes.length) {
const existingRootMesh = existingMeshes[0];
const swapZone = wallZones[currentZoneIndex];
if (swapZone) {
const offsetDistance = -0.05;
const swapPosition = swapZone.center.add(swapZone.normal.scale(offsetDistance));
existingRootMesh.position.copyFrom(swapPosition);
const targetDirection = swapZone.normal.scale(-1);
const angle = Math.atan2(targetDirection.x, targetDirection.z);
existingRootMesh.rotation.y = angle;
console.log(`[边界检测] 已将模型 ${existingModelId} 物理移动到区域 ${currentZoneIndex}`);
}
}
}
} else {
console.log(`[边界检测] 未启用替换模式或未启用占用区域处理,允许重叠`);
}
}
appDropZone["zoneModelMap"]?.set(newKey, modelId);
console.log(`[边界检测] 添加新映射: ${newKey} -> ${modelId}`);
} else {
console.log(`[边界检测] 模型 ${modelId} 仍在区域 ${currentZoneIndex},无需更新映射`);
}
}
/**
* 清理资源
*/
@ -356884,6 +357249,23 @@ clipPos=viewProjection*worldPos;previousClipPos=previousViewProjection*previousW
line.isVisible = true;
});
}
/**
* 只显示指定墙面的放置区域
* @param wallName 墙面名称
*/
showWall(wallName) {
this.hide();
this.placementZones.forEach((zone) => {
if (zone.wallName === wallName) {
zone.mesh.isVisible = true;
}
});
this.borderLines.forEach((line) => {
if (line.name.includes(`_${wallName}_`)) {
line.isVisible = true;
}
});
}
/**
* 隐藏所有放置区域
*/
@ -356920,6 +357302,7 @@ clipPos=viewProjection*worldPos;previousClipPos=previousViewProjection*previousW
scene;
placementWall;
appModel = null;
mainApp = null;
// 内部映射:放置区域 -> 模型ID
zoneModelMap = /* @__PURE__ */ new Map();
// 墙面 -> 当前分割数
@ -356940,6 +357323,12 @@ clipPos=viewProjection*worldPos;previousClipPos=previousViewProjection*previousW
setModelManager(appModel) {
this.appModel = appModel;
}
/**
* 设置 MainApp 引用内部使用
*/
setMainApp(mainApp) {
this.mainApp = mainApp;
}
/**
* 设置放置区域数据
* @param config 配置参数
@ -357018,6 +357407,9 @@ clipPos=viewProjection*worldPos;previousClipPos=previousViewProjection*previousW
divisions: newDivisions
};
}).filter((wall) => wall !== null);
this.dropZoneConfig.walls.forEach((wall) => {
this.wallDivisionsMap.set(wall.name, wall.divisions);
});
this.clearZones();
const zones = this.generateDropZones();
this.show();
@ -357071,6 +357463,176 @@ clipPos=viewProjection*worldPos;previousClipPos=previousViewProjection*previousW
}
this.zoneModelMap.set(zoneKey, modelId);
console.log(`已记录模型 ${modelId} 到区域 ${zoneKey}`);
this.checkAndAutoArrange(wallName);
}
/**
* 通知模型被删除外部调用用于更新映射和重新启用拖拽
* @param modelId 被删除的模型ID
*/
notifyModelRemoved(modelId) {
console.log(`[模型删除通知] 模型 ${modelId} 被删除`);
let removedWallName = null;
let removedZoneKey = null;
this.zoneModelMap.forEach((id, zoneKey) => {
if (id === modelId) {
removedZoneKey = zoneKey;
const match = zoneKey.match(/^(.+)\[(\d+)\]$/);
if (match) {
removedWallName = match[1];
}
}
});
if (removedZoneKey) {
this.zoneModelMap.delete(removedZoneKey);
console.log(`[模型删除通知] 已从映射中删除: ${removedZoneKey}`);
}
if (removedWallName) {
this.checkAndReenableDrag(removedWallName);
}
}
/**
* 检查墙面是否不满如果不满则重新启用该墙面所有模型的拖拽
* @param wallName 墙面名称
*/
checkAndReenableDrag(wallName) {
const currentDivisions = this.wallDivisionsMap.get(wallName);
if (!currentDivisions) return;
let placedCount = 0;
const placedModelIds = [];
this.zoneModelMap.forEach((modelId, zoneKey) => {
if (zoneKey.startsWith(`${wallName}[`)) {
placedCount++;
placedModelIds.push(modelId);
}
});
console.log(`[拖拽检查] 墙面 ${wallName} 当前模型数: ${placedCount}/${currentDivisions}`);
if (placedCount < currentDivisions) {
console.log(`[拖拽检查] 墙面 ${wallName} 未满,重新启用拖拽`);
placedModelIds.forEach((modelId) => {
if (this.mainApp && this.mainApp.appModelDrag && typeof this.mainApp.appModelDrag.setDragEnabled === "function") {
this.mainApp.appModelDrag.setDragEnabled(modelId, true);
console.log(`[拖拽检查] ✓ 已启用模型 ${modelId} 的拖拽功能`);
}
});
}
}
/**
* 检查墙面是否已满
* @param wallName 墙面名称
* @returns 是否已满
*/
isWallFull(wallName) {
const currentDivisions = this.wallDivisionsMap.get(wallName);
if (!currentDivisions) return false;
let placedCount = 0;
this.zoneModelMap.forEach((modelId, zoneKey) => {
if (zoneKey.startsWith(`${wallName}[`)) {
placedCount++;
}
});
return placedCount >= currentDivisions;
}
/**
* 检查墙面是否已满如果满了则自动排列模型
* @param wallName 墙面名称
*/
checkAndAutoArrange(wallName) {
const currentDivisions = this.wallDivisionsMap.get(wallName);
console.log(`[自动排列检查] 墙面: ${wallName}, 分割数: ${currentDivisions}`);
if (!currentDivisions) {
console.log(`[自动排列检查] 墙面 ${wallName} 没有分割数配置,跳过`);
return;
}
let placedCount = 0;
const placedModels = [];
this.zoneModelMap.forEach((modelId, zoneKey) => {
if (zoneKey.startsWith(`${wallName}[`)) {
placedCount++;
placedModels.push(`${zoneKey} -> ${modelId}`);
}
});
console.log(`[自动排列检查] 墙面 ${wallName} 已放置模型数: ${placedCount}/${currentDivisions}`);
console.log(`[自动排列检查] 已放置的模型:`, placedModels);
if (placedCount === currentDivisions) {
console.log(`[自动排列] 墙面 ${wallName} 已满(${placedCount}/${currentDivisions}),开始执行自动排列`);
this.autoArrangeWall(wallName);
} else {
console.log(`[自动排列检查] 墙面 ${wallName} 未满,不执行自动排列`);
}
}
/**
* 自动排列墙面上的所有模型
* @param wallName 墙面名称
*/
autoArrangeWall(wallName) {
console.log(`[自动排列] 开始排列墙面: ${wallName}`);
const wallZones = this.getZonesByWall(wallName);
console.log(`[自动排列] 墙面 ${wallName} 的放置区域数量: ${wallZones.length}`);
if (!wallZones.length) {
console.log(`[自动排列] 墙面 ${wallName} 没有放置区域,退出`);
return;
}
const placedModels = [];
this.zoneModelMap.forEach((modelId, zoneKey) => {
const match = zoneKey.match(new RegExp(`^${wallName}\\[(\\d+)\\]$`));
if (match) {
const currentIndex = parseInt(match[1]);
placedModels.push({
modelId,
currentIndex
});
console.log(`[自动排列] 找到模型: ${modelId}, 当前索引: ${currentIndex}`);
}
});
console.log(`[自动排列] 收集到 ${placedModels.length} 个模型`);
placedModels.sort((a, b) => a.currentIndex - b.currentIndex);
console.log(`[自动排列] 排序后的模型顺序:`, placedModels.map((m) => `${m.modelId}(索引${m.currentIndex})`));
placedModels.forEach((model, newIndex) => {
console.log(`[自动排列] 处理模型 ${model.modelId}: 当前索引=${model.currentIndex}, 目标索引=${newIndex}`);
const targetZone = wallZones[newIndex];
if (!targetZone) {
console.warn(`[自动排列] ✗ 找不到索引 ${newIndex} 的放置区域`);
return;
}
if (this.appModel) {
const offsetDistance = 0;
const targetPosition = targetZone.center.add(targetZone.normal.scale(offsetDistance));
const targetDirection = targetZone.normal.scale(-1);
const angle = Math.atan2(targetDirection.x, targetDirection.z);
console.log(`[自动排列] 目标区域 ${newIndex} 的位置:`, {
center: targetZone.center,
normal: targetZone.normal,
targetPosition,
rotation: angle * 180 / Math.PI
});
const meshes = this.appModel.getCachedMeshes(model.modelId);
if (meshes && meshes.length > 0) {
const rootMesh = meshes[0];
rootMesh.position.copyFrom(targetPosition);
rootMesh.rotation.y = angle;
console.log(`[自动排列] ✓ 模型 ${model.modelId} 已移动到索引 ${newIndex} 的位置`);
} else {
console.warn(`[自动排列] ✗ 找不到模型 ${model.modelId} 的网格`);
}
if (model.currentIndex !== newIndex) {
const oldKey = `${wallName}[${model.currentIndex}]`;
const newKey = `${wallName}[${newIndex}]`;
this.zoneModelMap.delete(oldKey);
this.zoneModelMap.set(newKey, model.modelId);
console.log(`[自动排列] 更新映射: ${oldKey} -> ${newKey}`);
}
}
});
console.log(`[自动排列] 开始禁用拖拽功能`);
placedModels.forEach((model) => {
if (this.mainApp && this.mainApp.appModelDrag && typeof this.mainApp.appModelDrag.setDragEnabled === "function") {
this.mainApp.appModelDrag.setDragEnabled(model.modelId, false);
console.log(`[自动排列] ✓ 已禁用模型 ${model.modelId} 的拖拽功能`);
} else {
console.warn(`[自动排列] ✗ 无法禁用模型 ${model.modelId} 的拖拽功能appModelDrag 未初始化`);
}
});
console.log(`[自动排列] 墙面 ${wallName} 自动排列完成`);
}
/**
* 获取所有放置区域
@ -357101,12 +357663,37 @@ clipPos=viewProjection*worldPos;previousClipPos=previousViewProjection*previousW
*/
show() {
this.placementWall.show();
this.setModelsPickable(false);
}
/**
* 只显示指定墙面的放置区域
* @param wallName 墙面名称
*/
showWall(wallName) {
this.placementWall.showWall(wallName);
this.setModelsPickable(false);
}
/**
* 隐藏所有放置区域
*/
hide() {
this.placementWall.hide();
this.setModelsPickable(true);
}
/**
* 设置所有已放置模型的可拾取状态
* @param pickable 是否可拾取
*/
setModelsPickable(pickable) {
if (!this.appModel) return;
this.zoneModelMap.forEach((modelId) => {
const meshes = this.appModel.getCachedMeshes(modelId);
if (meshes && meshes.length > 0) {
meshes.forEach((mesh) => {
mesh.isPickable = pickable;
});
}
});
}
/**
* 清除所有放置区域只清除网格不清除模型
@ -357501,6 +358088,7 @@ clipPos=viewProjection*worldPos;previousClipPos=previousViewProjection*previousW
if (this.config.receiveShadows) {
this.ground.receiveShadows = true;
}
this.ground.isPickable = false;
}
/** 创建材质 */
createMaterial() {
@ -357539,6 +358127,7 @@ clipPos=viewProjection*worldPos;previousClipPos=previousViewProjection*previousW
const gridPosition = this.config.position ? this.config.position.clone() : new Vector3(0, 0, 0);
gridPosition.y += 0.01;
this.gridGround.position = gridPosition;
this.gridGround.isPickable = false;
this.gridMaterial = new GridMaterial("gridMaterial", this.mainApp.appScene.object);
this.gridMaterial.mainColor = this.config.gridColor || new Color3(0.3, 0.3, 0.3);
this.gridMaterial.lineColor = this.config.gridColor || new Color3(0.3, 0.3, 0.3);
@ -357663,6 +358252,7 @@ clipPos=viewProjection*worldPos;previousClipPos=previousViewProjection*previousW
this.appModel.initManagers();
this.appDropZone = new AppDropZone(this.appScene.object);
this.appDropZone.setModelManager(this.appModel);
this.appDropZone.setMainApp(this);
this.update();
EventBridge.sceneReady({ scene: this.appScene.object });
}