1
This commit is contained in:
@ -181,44 +181,48 @@ const getEvent = async (dropzone_data, sku) => {
|
|||||||
|
|
||||||
//点击放置区域执行事件 一般是换配件
|
//点击放置区域执行事件 一般是换配件
|
||||||
const executeEvent = async (dropzone_data, result, sku) => {
|
const executeEvent = async (dropzone_data, result, sku) => {
|
||||||
const kernel = getKernel();
|
const kernel = getKernel();
|
||||||
|
|
||||||
const { wallName, index, transform } = dropzone_data;
|
const { wallName, index, transform } = dropzone_data;
|
||||||
const { position, rotation } = transform;
|
const { position, rotation } = transform;
|
||||||
|
|
||||||
let modelId = null; // 在外部声明,用于在两个循环之间传递
|
let modelId = null; // 在外部声明,用于在两个循环之间传递
|
||||||
let modelName = null;
|
let modelName = null;
|
||||||
let pergolaSku = null; // 用于存储棚子的 SKU
|
let pergolaSku = null; // 用于存储棚子的 SKU
|
||||||
|
|
||||||
// 第一次循环:处理 change_model
|
// 第一次循环:处理 change_model
|
||||||
for (const event of result.data.events) {
|
for (const event of result.data.events) {
|
||||||
if (event.event_type === 'change_model') {
|
if (event.event_type === 'change_model') {
|
||||||
const { name, file_url, model_control_type, category } = event.target_data;
|
const { name, file_url, model_control_type, category } = event.target_data;
|
||||||
|
|
||||||
// 生成唯一的模型ID
|
// 生成唯一的模型ID
|
||||||
modelId = Date.now();
|
modelId = Date.now();
|
||||||
modelName = name;
|
modelName = name;
|
||||||
kernel.dropZone.recordModelPlacement(wallName, index, name + '_' + modelId);
|
kernel.dropZone.recordModelPlacement(wallName, index, name + '_' + modelId);
|
||||||
|
|
||||||
await kernel.model.add({
|
await kernel.model.add({
|
||||||
modelName: name,
|
modelName: name,
|
||||||
modelId: modelId,
|
modelId: modelId,
|
||||||
modelUrl: file_url,
|
modelUrl: file_url,
|
||||||
modelControlType: model_control_type,
|
modelControlType: model_control_type,
|
||||||
drag: {
|
drag: {
|
||||||
enable: true,
|
enable: true,
|
||||||
axis: rotation.y === 0 || rotation.y === 180 ? 'x' : 'z',
|
axis: rotation.y === 0 || rotation.y === 180 ? 'x' : 'z',
|
||||||
step: 0.1,
|
step: 0.1,
|
||||||
},
|
snapToZone: true, // 拖拽吸附到最近的分割区域
|
||||||
transform: {
|
returnWhenOutOfBounds: true, // 拖拽到区域外时返回原位置
|
||||||
position: position,
|
handleOccupiedZone: true, // 处理已占用区域(false=允许重叠)
|
||||||
rotation: rotation,
|
occupiedZoneAction: 'replace' // 当开关3=true时的行为:'return'=返回原位置,'replace'=替换
|
||||||
}
|
},
|
||||||
});
|
transform: {
|
||||||
|
position: position,
|
||||||
|
rotation: rotation,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
console.log(`百叶模型已放置为 ${name + '_' + modelId}`);
|
console.log(`百叶模型已放置为 ${name + '_' + modelId}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 第二次循环:处理 change_color(此时模型已加载完成)
|
// 第二次循环:处理 change_color(此时模型已加载完成)
|
||||||
for (const event of result.data.events) {
|
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();
|
const kernel = getKernel();
|
||||||
|
|
||||||
// 检查是否有模型更换事件
|
// 检查是否有模型更换事件
|
||||||
@ -418,8 +422,8 @@ const getProductConfig = async (sku) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// API 配置
|
// 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) => {
|
const getApiUrl = (path) => {
|
||||||
return `${API_BASE_URL}${path}`;
|
return `${API_BASE_URL}${path}`;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -453,7 +453,7 @@
|
|||||||
<div class="category-content expanded">
|
<div class="category-content expanded">
|
||||||
<div class="option-group">
|
<div class="option-group">
|
||||||
<button class="option-btn" data-option="color-1">SPFPDS13FTW</button>
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -870,10 +870,6 @@
|
|||||||
|
|
||||||
console.log(kernel);
|
console.log(kernel);
|
||||||
|
|
||||||
// 监听放置区域点击事件
|
|
||||||
kernel.on('dropzone:click', function (dropzone_data) {
|
|
||||||
window.AppLogic.getEvent(dropzone_data, sku);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 存储当前选中的材质名和网格
|
// 存储当前选中的材质名和网格
|
||||||
var currentMaterialName = '';
|
var currentMaterialName = '';
|
||||||
|
|||||||
@ -221,33 +221,40 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 进度条样式 */
|
/* 加载遮罩样式 */
|
||||||
#progress-container {
|
#progress-container {
|
||||||
position: absolute;
|
position: fixed;
|
||||||
top: 50%;
|
top: 0;
|
||||||
left: 50%;
|
left: 0;
|
||||||
transform: translate(-50%, -50%);
|
width: 100vw;
|
||||||
width: 80%;
|
height: 100vh;
|
||||||
max-width: 500px;
|
background: rgba(0, 0, 0, 0.7);
|
||||||
background: rgba(255, 255, 255, 0.2);
|
display: flex;
|
||||||
border-radius: 10px;
|
flex-direction: column;
|
||||||
padding: 10px;
|
justify-content: center;
|
||||||
z-index: 1000;
|
align-items: center;
|
||||||
|
z-index: 9999;
|
||||||
}
|
}
|
||||||
|
|
||||||
#progress-bar {
|
.spinner {
|
||||||
width: 0%;
|
width: 50px;
|
||||||
height: 10px;
|
height: 50px;
|
||||||
background: linear-gradient(90deg, #4CAF50, #45a049);
|
border: 4px solid rgba(255, 255, 255, 0.3);
|
||||||
border-radius: 5px;
|
border-top-color: #4CAF50;
|
||||||
transition: width 0.1s ease;
|
border-radius: 50%;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
#progress-text {
|
@keyframes spin {
|
||||||
color: white;
|
to {
|
||||||
text-align: center;
|
transform: rotate(360deg);
|
||||||
margin-top: 5px;
|
}
|
||||||
font-size: 14px;
|
}
|
||||||
|
|
||||||
|
.loading-text {
|
||||||
|
color: #fff;
|
||||||
|
font-size: 16px;
|
||||||
|
margin-top: 20px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
@ -258,8 +265,8 @@
|
|||||||
<div id="canvas-container">
|
<div id="canvas-container">
|
||||||
<canvas id="renderDom"></canvas>
|
<canvas id="renderDom"></canvas>
|
||||||
<div id="progress-container" style="display: none;">
|
<div id="progress-container" style="display: none;">
|
||||||
<div id="progress-bar"></div>
|
<div class="spinner"></div>
|
||||||
<div id="progress-text">0%</div>
|
<div class="loading-text">加载中...</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 生成放置区域按钮 -->
|
<!-- 生成放置区域按钮 -->
|
||||||
@ -847,9 +854,25 @@
|
|||||||
|
|
||||||
// 监听放置区域点击事件
|
// 监听放置区域点击事件
|
||||||
kernel.on('dropzone:click', async (dropzone_data) => {
|
kernel.on('dropzone:click', async (dropzone_data) => {
|
||||||
|
// 显示进度条
|
||||||
|
const progressContainer = document.getElementById('progress-container');
|
||||||
|
if (progressContainer) {
|
||||||
|
progressContainer.style.display = 'flex';
|
||||||
|
}
|
||||||
|
|
||||||
getEvent(dropzone_data, sku)
|
getEvent(dropzone_data, sku)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 监听模型加载完成事件
|
||||||
|
kernel.on('model:loaded', (data) => {
|
||||||
|
console.log('模型加载完成', data);
|
||||||
|
// 隐藏进度条
|
||||||
|
const progressContainer = document.getElementById('progress-container');
|
||||||
|
if (progressContainer) {
|
||||||
|
progressContainer.style.display = 'none';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -79877,10 +79877,8 @@ discard;}}
|
|||||||
}
|
}
|
||||||
/** 初始化灯光并开启阴影 */
|
/** 初始化灯光并开启阴影 */
|
||||||
Awake() {
|
Awake() {
|
||||||
new DirectionalLight(
|
const light = new DirectionalLight("sun", new Vector3(-1, -2, -1), this.mainApp.appScene.object);
|
||||||
"mainLight",
|
light.intensity = 1.2;
|
||||||
new Vector3(0, -0.5, -1)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -354748,6 +354746,22 @@ clipPos=viewProjection*worldPos;previousClipPos=previousViewProjection*previousW
|
|||||||
}
|
}
|
||||||
if (drag) {
|
if (drag) {
|
||||||
this.mainApp.appModelDrag?.configureDrag(modelName + "_" + modelId, 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();
|
this.mainApp.gameManager?.updateDictionaries();
|
||||||
EventBridge.modelLoaded({ urls: [modelUrl] });
|
EventBridge.modelLoaded({ urls: [modelUrl] });
|
||||||
@ -354907,6 +354921,9 @@ clipPos=viewProjection*worldPos;previousClipPos=previousViewProjection*previousW
|
|||||||
this.modelDic.Remove(modelName);
|
this.modelDic.Remove(modelName);
|
||||||
this.modelMetadataDic.Remove(modelName);
|
this.modelMetadataDic.Remove(modelName);
|
||||||
this.mainApp.gameManager?.updateDictionaries();
|
this.mainApp.gameManager?.updateDictionaries();
|
||||||
|
if (this.mainApp.appDropZone && typeof this.mainApp.appDropZone.notifyModelRemoved === "function") {
|
||||||
|
this.mainApp.appDropZone.notifyModelRemoved(modelName);
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
@ -355067,9 +355084,10 @@ clipPos=viewProjection*worldPos;previousClipPos=previousViewProjection*previousW
|
|||||||
* 将模型放置到指定的放置区域
|
* 将模型放置到指定的放置区域
|
||||||
* @param modelId 模型ID
|
* @param modelId 模型ID
|
||||||
* @param zoneInfo 放置区域信息
|
* @param zoneInfo 放置区域信息
|
||||||
* @param offsetDistance 距离墙面的偏移距离(默认0.1,正数向外)
|
* @param offsetDistance 距离墙面的偏移距离(默认0,正数向外)
|
||||||
*/
|
*/
|
||||||
placeToZone(modelId, zoneInfo, offsetDistance = 0) {
|
placeToZone(modelId, zoneInfo, offsetDistance = 0) {
|
||||||
|
console.log(zoneInfo);
|
||||||
const meshes = this.modelDic.Get(modelId);
|
const meshes = this.modelDic.Get(modelId);
|
||||||
if (!meshes?.length) {
|
if (!meshes?.length) {
|
||||||
console.warn(`Model not found: ${modelId}`);
|
console.warn(`Model not found: ${modelId}`);
|
||||||
@ -355119,6 +355137,12 @@ clipPos=viewProjection*worldPos;previousClipPos=previousViewProjection*previousW
|
|||||||
highlightLayer = null;
|
highlightLayer = null;
|
||||||
originalMaterial = null;
|
originalMaterial = null;
|
||||||
highlightedMesh = null;
|
highlightedMesh = null;
|
||||||
|
pointerDownTime = 0;
|
||||||
|
pointerDownPickInfo = null;
|
||||||
|
longPressTimer = null;
|
||||||
|
longPressThreshold = 500;
|
||||||
|
// 长按阈值(毫秒)
|
||||||
|
isLongPress = false;
|
||||||
constructor(mainApp) {
|
constructor(mainApp) {
|
||||||
super(mainApp);
|
super(mainApp);
|
||||||
}
|
}
|
||||||
@ -355143,29 +355167,85 @@ clipPos=viewProjection*worldPos;previousClipPos=previousViewProjection*previousW
|
|||||||
}
|
}
|
||||||
if (type === PointerEventTypes.POINTERDOWN) {
|
if (type === PointerEventTypes.POINTERDOWN) {
|
||||||
this.oldPoint.set(pointerEvent.clientX, 0, pointerEvent.clientY);
|
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) {
|
} else if (type === PointerEventTypes.POINTERUP) {
|
||||||
|
if (this.longPressTimer) {
|
||||||
|
clearTimeout(this.longPressTimer);
|
||||||
|
this.longPressTimer = null;
|
||||||
|
}
|
||||||
this.newPoint.set(pointerEvent.clientX, 0, pointerEvent.clientY);
|
this.newPoint.set(pointerEvent.clientX, 0, pointerEvent.clientY);
|
||||||
const distance = Vector3.Distance(this.oldPoint, this.newPoint);
|
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.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) {
|
handleSingleClick(evt, pickInfo) {
|
||||||
if (pickInfo && pickInfo.hit && pickInfo.pickedMesh && pickInfo.pickedPoint) {
|
if (pickInfo && pickInfo.hit && pickInfo.pickedMesh && pickInfo.pickedPoint) {
|
||||||
if (pickInfo.pickedMesh.metadata?.type === "hotspot") {
|
if (pickInfo.pickedMesh.metadata?.type === "hotspot") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (pickInfo.pickedMesh.name === "gridGround" || pickInfo.pickedMesh.name === "ground" || pickInfo.pickedMesh.name === "sphere_yundong_mesh") {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (pickInfo.pickedMesh.name.startsWith("placement_")) {
|
if (pickInfo.pickedMesh.name.startsWith("placement_")) {
|
||||||
const zones = this.mainApp.appDropZone.getPlacementZones();
|
const zones = this.mainApp.appDropZone.getPlacementZones();
|
||||||
const clickedZone = zones.find((zone) => zone.mesh === pickInfo.pickedMesh);
|
const clickedZone = zones.find((zone) => zone.mesh === pickInfo.pickedMesh);
|
||||||
if (clickedZone) {
|
if (clickedZone) {
|
||||||
const offsetDistance = 0.05;
|
const offsetDistance = 0;
|
||||||
const targetPosition = clickedZone.center.add(clickedZone.normal.scale(offsetDistance));
|
const targetPosition = clickedZone.center.add(clickedZone.normal.scale(offsetDistance));
|
||||||
const targetDirection = clickedZone.normal.scale(-1);
|
const targetDirection = clickedZone.normal.scale(-1);
|
||||||
const angle = Math.atan2(targetDirection.x, targetDirection.z);
|
const angle = Math.atan2(targetDirection.x, targetDirection.z);
|
||||||
@ -355186,7 +355266,6 @@ clipPos=viewProjection*worldPos;previousClipPos=previousViewProjection*previousW
|
|||||||
rotation: {
|
rotation: {
|
||||||
x: 0,
|
x: 0,
|
||||||
y: angle * 180 / Math.PI,
|
y: angle * 180 / Math.PI,
|
||||||
// 转换为角度
|
|
||||||
z: 0
|
z: 0
|
||||||
},
|
},
|
||||||
scale: {
|
scale: {
|
||||||
@ -355200,6 +355279,7 @@ clipPos=viewProjection*worldPos;previousClipPos=previousViewProjection*previousW
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.mainApp.appDomTo3D.hideAll();
|
this.mainApp.appDomTo3D.hideAll();
|
||||||
|
this.mainApp.appDropZone.hide();
|
||||||
const materialName = pickInfo.pickedMesh.material?.name || "";
|
const materialName = pickInfo.pickedMesh.material?.name || "";
|
||||||
const holdingShift = Boolean(evt.shiftKey);
|
const holdingShift = Boolean(evt.shiftKey);
|
||||||
const modelMeshes = this.mainApp.appModel.getModelMeshesByMesh(pickInfo.pickedMesh);
|
const modelMeshes = this.mainApp.appModel.getModelMeshesByMesh(pickInfo.pickedMesh);
|
||||||
@ -355222,7 +355302,6 @@ clipPos=viewProjection*worldPos;previousClipPos=previousViewProjection*previousW
|
|||||||
modelControlType: modelMetadata?.modelControlType
|
modelControlType: modelMetadata?.modelControlType
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
console.log(1111);
|
|
||||||
this.mainApp.appSelectionOutline.clear();
|
this.mainApp.appSelectionOutline.clear();
|
||||||
this.mainApp.appPositionGizmo.detach();
|
this.mainApp.appPositionGizmo.detach();
|
||||||
this.mainApp.appDomTo3D.hideAll();
|
this.mainApp.appDomTo3D.hideAll();
|
||||||
@ -356535,11 +356614,44 @@ clipPos=viewProjection*worldPos;previousClipPos=previousViewProjection*previousW
|
|||||||
}
|
}
|
||||||
const pointerDragBehavior = new PointerDragBehavior({ dragAxis });
|
const pointerDragBehavior = new PointerDragBehavior({ dragAxis });
|
||||||
pointerDragBehavior.useObjectOrientationForDragging = false;
|
pointerDragBehavior.useObjectOrientationForDragging = false;
|
||||||
|
let dragStartPosition = null;
|
||||||
|
let hasShownZones = false;
|
||||||
pointerDragBehavior.onDragStartObservable.add(() => {
|
pointerDragBehavior.onDragStartObservable.add(() => {
|
||||||
|
const meshes = this.mainApp.appModel?.modelDic?.Get(modelId);
|
||||||
|
if (meshes && meshes.length > 0) {
|
||||||
|
dragStartPosition = meshes[0].position.clone();
|
||||||
|
}
|
||||||
this.disableCameraControl();
|
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(() => {
|
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();
|
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;
|
return pointerDragBehavior;
|
||||||
}
|
}
|
||||||
@ -356632,6 +356744,259 @@ clipPos=viewProjection*worldPos;previousClipPos=previousViewProjection*previousW
|
|||||||
camera.attachControl(canvas, true);
|
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;
|
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;
|
scene;
|
||||||
placementWall;
|
placementWall;
|
||||||
appModel = null;
|
appModel = null;
|
||||||
|
mainApp = null;
|
||||||
// 内部映射:放置区域 -> 模型ID
|
// 内部映射:放置区域 -> 模型ID
|
||||||
zoneModelMap = /* @__PURE__ */ new Map();
|
zoneModelMap = /* @__PURE__ */ new Map();
|
||||||
// 墙面 -> 当前分割数
|
// 墙面 -> 当前分割数
|
||||||
@ -356940,6 +357323,12 @@ clipPos=viewProjection*worldPos;previousClipPos=previousViewProjection*previousW
|
|||||||
setModelManager(appModel) {
|
setModelManager(appModel) {
|
||||||
this.appModel = appModel;
|
this.appModel = appModel;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* 设置 MainApp 引用(内部使用)
|
||||||
|
*/
|
||||||
|
setMainApp(mainApp) {
|
||||||
|
this.mainApp = mainApp;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* 设置放置区域数据
|
* 设置放置区域数据
|
||||||
* @param config 配置参数
|
* @param config 配置参数
|
||||||
@ -357018,6 +357407,9 @@ clipPos=viewProjection*worldPos;previousClipPos=previousViewProjection*previousW
|
|||||||
divisions: newDivisions
|
divisions: newDivisions
|
||||||
};
|
};
|
||||||
}).filter((wall) => wall !== null);
|
}).filter((wall) => wall !== null);
|
||||||
|
this.dropZoneConfig.walls.forEach((wall) => {
|
||||||
|
this.wallDivisionsMap.set(wall.name, wall.divisions);
|
||||||
|
});
|
||||||
this.clearZones();
|
this.clearZones();
|
||||||
const zones = this.generateDropZones();
|
const zones = this.generateDropZones();
|
||||||
this.show();
|
this.show();
|
||||||
@ -357071,6 +357463,176 @@ clipPos=viewProjection*worldPos;previousClipPos=previousViewProjection*previousW
|
|||||||
}
|
}
|
||||||
this.zoneModelMap.set(zoneKey, modelId);
|
this.zoneModelMap.set(zoneKey, modelId);
|
||||||
console.log(`已记录模型 ${modelId} 到区域 ${zoneKey}`);
|
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() {
|
show() {
|
||||||
this.placementWall.show();
|
this.placementWall.show();
|
||||||
|
this.setModelsPickable(false);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 只显示指定墙面的放置区域
|
||||||
|
* @param wallName 墙面名称
|
||||||
|
*/
|
||||||
|
showWall(wallName) {
|
||||||
|
this.placementWall.showWall(wallName);
|
||||||
|
this.setModelsPickable(false);
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* 隐藏所有放置区域
|
* 隐藏所有放置区域
|
||||||
*/
|
*/
|
||||||
hide() {
|
hide() {
|
||||||
this.placementWall.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) {
|
if (this.config.receiveShadows) {
|
||||||
this.ground.receiveShadows = true;
|
this.ground.receiveShadows = true;
|
||||||
}
|
}
|
||||||
|
this.ground.isPickable = false;
|
||||||
}
|
}
|
||||||
/** 创建材质 */
|
/** 创建材质 */
|
||||||
createMaterial() {
|
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);
|
const gridPosition = this.config.position ? this.config.position.clone() : new Vector3(0, 0, 0);
|
||||||
gridPosition.y += 0.01;
|
gridPosition.y += 0.01;
|
||||||
this.gridGround.position = gridPosition;
|
this.gridGround.position = gridPosition;
|
||||||
|
this.gridGround.isPickable = false;
|
||||||
this.gridMaterial = new GridMaterial("gridMaterial", this.mainApp.appScene.object);
|
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.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);
|
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.appModel.initManagers();
|
||||||
this.appDropZone = new AppDropZone(this.appScene.object);
|
this.appDropZone = new AppDropZone(this.appScene.object);
|
||||||
this.appDropZone.setModelManager(this.appModel);
|
this.appDropZone.setModelManager(this.appModel);
|
||||||
|
this.appDropZone.setMainApp(this);
|
||||||
this.update();
|
this.update();
|
||||||
EventBridge.sceneReady({ scene: this.appScene.object });
|
EventBridge.sceneReady({ scene: this.appScene.object });
|
||||||
}
|
}
|
||||||
|
|||||||
74
index.html
74
index.html
@ -275,33 +275,40 @@
|
|||||||
letter-spacing: 2px;
|
letter-spacing: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 进度条样式 */
|
/* 加载遮罩样式 */
|
||||||
#progress-container {
|
#progress-container {
|
||||||
position: absolute;
|
position: fixed;
|
||||||
top: 50%;
|
top: 0;
|
||||||
left: 50%;
|
left: 0;
|
||||||
transform: translate(-50%, -50%);
|
width: 100vw;
|
||||||
width: 80%;
|
height: 100vh;
|
||||||
max-width: 500px;
|
background: rgba(0, 0, 0, 0.7);
|
||||||
background: rgba(255, 255, 255, 0.2);
|
display: flex;
|
||||||
border-radius: 10px;
|
flex-direction: column;
|
||||||
padding: 10px;
|
justify-content: center;
|
||||||
z-index: 1000;
|
align-items: center;
|
||||||
|
z-index: 9999;
|
||||||
}
|
}
|
||||||
|
|
||||||
#progress-bar {
|
.spinner {
|
||||||
width: 0%;
|
width: 50px;
|
||||||
height: 10px;
|
height: 50px;
|
||||||
background: linear-gradient(90deg, #4CAF50, #45a049);
|
border: 4px solid rgba(255, 255, 255, 0.3);
|
||||||
border-radius: 5px;
|
border-top-color: #4CAF50;
|
||||||
transition: width 0.1s ease;
|
border-radius: 50%;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
#progress-text {
|
@keyframes spin {
|
||||||
color: white;
|
to {
|
||||||
text-align: center;
|
transform: rotate(360deg);
|
||||||
margin-top: 5px;
|
}
|
||||||
font-size: 14px;
|
}
|
||||||
|
|
||||||
|
.loading-text {
|
||||||
|
color: #fff;
|
||||||
|
font-size: 16px;
|
||||||
|
margin-top: 20px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
@ -312,8 +319,8 @@
|
|||||||
<div id="canvas-container">
|
<div id="canvas-container">
|
||||||
<canvas id="renderDom"></canvas>
|
<canvas id="renderDom"></canvas>
|
||||||
<div id="progress-container" style="display: none;">
|
<div id="progress-container" style="display: none;">
|
||||||
<div id="progress-bar"></div>
|
<div class="spinner"></div>
|
||||||
<div id="progress-text">0%</div>
|
<div class="loading-text">加载中...</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 生成放置区域按钮 -->
|
<!-- 生成放置区域按钮 -->
|
||||||
@ -892,6 +899,7 @@
|
|||||||
|
|
||||||
// 移除按钮事件
|
// 移除按钮事件
|
||||||
document.getElementById('remove-model-btn').addEventListener('click', () => {
|
document.getElementById('remove-model-btn').addEventListener('click', () => {
|
||||||
|
|
||||||
const pickedMesh = window.getCurrentPickedMesh();
|
const pickedMesh = window.getCurrentPickedMesh();
|
||||||
if (pickedMesh) {
|
if (pickedMesh) {
|
||||||
const meshName = pickedMesh.name;
|
const meshName = pickedMesh.name;
|
||||||
@ -916,9 +924,25 @@
|
|||||||
|
|
||||||
// 监听放置区域点击事件
|
// 监听放置区域点击事件
|
||||||
kernel.on('dropzone:click', async (dropzone_data) => {
|
kernel.on('dropzone:click', async (dropzone_data) => {
|
||||||
|
// 显示进度条
|
||||||
|
const progressContainer = document.getElementById('progress-container');
|
||||||
|
if (progressContainer) {
|
||||||
|
progressContainer.style.display = 'flex';
|
||||||
|
}
|
||||||
|
|
||||||
getEvent(dropzone_data, sku)
|
getEvent(dropzone_data, sku)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 监听模型加载完成事件
|
||||||
|
kernel.on('model:loaded', (data) => {
|
||||||
|
console.log('模型加载完成', data);
|
||||||
|
// 隐藏进度条
|
||||||
|
const progressContainer = document.getElementById('progress-container');
|
||||||
|
if (progressContainer) {
|
||||||
|
progressContainer.style.display = 'none';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
// 存储当前选中的材质名和网格
|
// 存储当前选中的材质名和网格
|
||||||
let currentMaterialName = '';
|
let currentMaterialName = '';
|
||||||
@ -994,6 +1018,8 @@
|
|||||||
|
|
||||||
// 暴露 kernel 到全局,方便调试
|
// 暴露 kernel 到全局,方便调试
|
||||||
|
|
||||||
|
kernel.on('model:loaded', (event) => {
|
||||||
|
});
|
||||||
|
|
||||||
kernel.on('hotspot:click', (event) => {
|
kernel.on('hotspot:click', (event) => {
|
||||||
console.log('热点被点击:', event);
|
console.log('热点被点击:', event);
|
||||||
|
|||||||
17
index.js
17
index.js
@ -44,7 +44,8 @@ export const init = async (customConfig = {}) => {
|
|||||||
container: document.querySelector('#renderDom'),
|
container: document.querySelector('#renderDom'),
|
||||||
modelUrlList: [],
|
modelUrlList: [],
|
||||||
env: { envPath: 'https://cdn.files.zguiy.com/zt/environment.env', intensity: 1.2, rotationY: 0.3, background: false },
|
env: { envPath: 'https://cdn.files.zguiy.com/zt/environment.env', intensity: 1.2, rotationY: 0.3, background: false },
|
||||||
gizmo: {
|
gcamera:{},
|
||||||
|
izmo: {
|
||||||
position: false,
|
position: false,
|
||||||
rotation: false,
|
rotation: false,
|
||||||
scale: false
|
scale: false
|
||||||
@ -62,7 +63,7 @@ export const init = async (customConfig = {}) => {
|
|||||||
const config = { ...defaultConfig, ...customConfig };
|
const config = { ...defaultConfig, ...customConfig };
|
||||||
kernel.init(config);
|
kernel.init(config);
|
||||||
}
|
}
|
||||||
|
//
|
||||||
//初始化加载模型
|
//初始化加载模型
|
||||||
export const getAutoLoadModelList = async () => {
|
export const getAutoLoadModelList = async () => {
|
||||||
const kernel = getKernel();
|
const kernel = getKernel();
|
||||||
@ -201,6 +202,7 @@ export const executeEvent = async (dropzone_data, result, sku) => {
|
|||||||
// 第一次循环:处理 change_model
|
// 第一次循环:处理 change_model
|
||||||
for (const event of result.data.events) {
|
for (const event of result.data.events) {
|
||||||
if (event.event_type === 'change_model') {
|
if (event.event_type === 'change_model') {
|
||||||
|
|
||||||
const { name, file_url, model_control_type, category } = event.target_data;
|
const { name, file_url, model_control_type, category } = event.target_data;
|
||||||
|
|
||||||
// 生成唯一的模型ID
|
// 生成唯一的模型ID
|
||||||
@ -208,8 +210,7 @@ export const executeEvent = async (dropzone_data, result, sku) => {
|
|||||||
modelName = name;
|
modelName = name;
|
||||||
kernel.dropZone.recordModelPlacement(wallName, index, name + '_' + modelId);
|
kernel.dropZone.recordModelPlacement(wallName, index, name + '_' + modelId);
|
||||||
|
|
||||||
// 记录模型ID到SKU的映射
|
|
||||||
setSkuMapping(modelId, sku);
|
|
||||||
|
|
||||||
await kernel.model.add({
|
await kernel.model.add({
|
||||||
modelName: name,
|
modelName: name,
|
||||||
@ -220,10 +221,10 @@ export const executeEvent = async (dropzone_data, result, sku) => {
|
|||||||
enable: true,
|
enable: true,
|
||||||
axis: rotation.y === 0 || rotation.y === 180 ? 'x' : 'z',
|
axis: rotation.y === 0 || rotation.y === 180 ? 'x' : 'z',
|
||||||
step: 0.1,
|
step: 0.1,
|
||||||
// snapToZone: true, // 开关1:拖拽吸附到最近的分割区域
|
snapToZone: true, // 拖拽吸附到最近的分割区域
|
||||||
// returnWhenOutOfBounds: false, // 开关2:拖拽到区域外时返回原位置
|
returnWhenOutOfBounds: true, // 拖拽到区域外时返回原位置
|
||||||
// handleOccupiedZone: true, // 开关3:处理已占用区域(false=允许重叠)
|
handleOccupiedZone: true, // 处理已占用区域(false=允许重叠)
|
||||||
// occupiedZoneAction: 'return' // 当开关3=true时的行为:'return'=返回原位置,'replace'=替换
|
occupiedZoneAction: 'replace' // 当开关3=true时的行为:'return'=返回原位置,'replace'=替换
|
||||||
},
|
},
|
||||||
transform: {
|
transform: {
|
||||||
position: position,
|
position: position,
|
||||||
|
|||||||
@ -25,7 +25,7 @@ export class AppCamera extends Monobehiver {
|
|||||||
this.object = new ArcRotateCamera('Camera', Tools.ToRadians(70), Tools.ToRadians(85), 5, new Vector3(0, 2, 0), scene);
|
this.object = new ArcRotateCamera('Camera', Tools.ToRadians(70), Tools.ToRadians(85), 5, new Vector3(0, 2, 0), scene);
|
||||||
this.object.attachControl(canvas, true);
|
this.object.attachControl(canvas, true);
|
||||||
this.object.minZ = 0.01; // 近裁剪面
|
this.object.minZ = 0.01; // 近裁剪面
|
||||||
this.object.wheelPrecision =200; // 滚轮缩放精度
|
this.object.wheelPrecision =200; // 滚轮缩放精度
|
||||||
this.object.panningSensibility = 0;
|
this.object.panningSensibility = 0;
|
||||||
|
|
||||||
// 限制垂直角范围,实现上帝视角
|
// 限制垂直角范围,实现上帝视角
|
||||||
|
|||||||
Reference in New Issue
Block a user