1
This commit is contained in:
418
index copy.html
418
index copy.html
@ -67,6 +67,40 @@
|
||||
border-bottom: 2px solid rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.click-info {
|
||||
background: rgba(76, 175, 80, 0.2);
|
||||
border: 1px solid rgba(76, 175, 80, 0.5);
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
margin-bottom: 15px;
|
||||
color: #fff;
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.click-info-title {
|
||||
font-weight: bold;
|
||||
color: #4caf50;
|
||||
margin-bottom: 8px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.click-info-item {
|
||||
margin-bottom: 4px;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.click-info-label {
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
min-width: 70px;
|
||||
}
|
||||
|
||||
.click-info-value {
|
||||
color: #fff;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.config-category {
|
||||
margin-bottom: 15px;
|
||||
border-radius: 8px;
|
||||
@ -227,12 +261,34 @@
|
||||
<div id="progress-bar"></div>
|
||||
<div id="progress-text">0%</div>
|
||||
</div>
|
||||
|
||||
<!-- 生成放置区域按钮 -->
|
||||
<button id="dropzone-btn" style="
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
left: 20px;
|
||||
padding: 10px 20px;
|
||||
background: #21c7ff;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.3);
|
||||
z-index: 100;
|
||||
">生成放置区域</button>
|
||||
</div>
|
||||
|
||||
<!-- 配置面板 -->
|
||||
<div id="config-panel">
|
||||
<div class="config-title">选装选配</div>
|
||||
|
||||
<!-- 点击信息显示区域 -->
|
||||
<div id="click-info" class="click-info" style="display: none;">
|
||||
<div class="click-info-title">点击信息</div>
|
||||
<div id="click-info-content"></div>
|
||||
</div>
|
||||
|
||||
<!-- 棚子尺寸 -->
|
||||
<div class="config-category">
|
||||
<div class="category-header" data-category="size">
|
||||
@ -273,7 +329,7 @@
|
||||
</div>
|
||||
<div class="category-content">
|
||||
<div class="option-group">
|
||||
<button class="option-btn" data-option="louver-1">百叶1</button>
|
||||
<button class="option-btn" data-option="louver-1">111</button>
|
||||
<button class="option-btn" data-option="louver-2">百叶2</button>
|
||||
<button class="option-btn" data-option="louver-3">百叶3</button>
|
||||
<button class="option-btn" data-option="louver-4">百叶4</button>
|
||||
@ -290,13 +346,16 @@
|
||||
</div>
|
||||
<div class="category-content">
|
||||
<div class="option-group">
|
||||
<button class="option-btn" data-option="color-1">白色</button>
|
||||
<button class="option-btn" data-option="color-1">222222</button>
|
||||
<button class="option-btn" data-option="color-2">灰色</button>
|
||||
<button class="option-btn" data-option="color-3">黑色</button>
|
||||
<button class="option-btn" data-option="color-4">木色</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button id="hotspot-btn">生成热点</button>
|
||||
<button id="prevent-btn">生成防止区域</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -390,6 +449,10 @@
|
||||
|
||||
<script type="module" src="./index.js"></script>
|
||||
<script type="module">
|
||||
|
||||
|
||||
|
||||
|
||||
import { kernel } from './src/main.ts';
|
||||
|
||||
// ========== UI 交互逻辑 ==========
|
||||
@ -439,48 +502,177 @@
|
||||
});
|
||||
|
||||
// 百叶模型替换逻辑
|
||||
if (categoryName === "louver") {
|
||||
const currentText = this.textContent;
|
||||
// if (categoryName === "louver") {
|
||||
const currentText = this.textContent;
|
||||
console.log(currentText);
|
||||
|
||||
// 根据 SKU 查询配置和事件
|
||||
try {
|
||||
const response = await fetch(`http://localhost:3000/api/product-configs/by-sku/${currentText}`);
|
||||
const result = await response.json();
|
||||
|
||||
if (result.code === 200 && result.data) {
|
||||
console.log('SKU配置数据:', result.data);
|
||||
console.log('关联事件:', result.data.events);
|
||||
|
||||
// 使用配置数据中的模型路径(如果有)
|
||||
const modelUrl = result.data.model_id
|
||||
? `https://sdk.zguiy.com/resurces/model/${result.data.model_id}.glb`
|
||||
: `https://sdk.zguiy.com/resurces/model/${currentText}.glb`;
|
||||
|
||||
console.log('替换百叶模型:', modelUrl);
|
||||
await kernel.model.replace({
|
||||
modelId: '卷帘小',
|
||||
modelUrl: modelUrl,
|
||||
modelControlType: 'color'
|
||||
});
|
||||
console.log(`百叶模型已替换为 ${currentText}`);
|
||||
} else {
|
||||
console.warn('未找到SKU配置,使用默认模型路径');
|
||||
const modelUrl = `https://sdk.zguiy.com/resurces/model/${currentText}.glb`;
|
||||
console.log('替换百叶模型:', modelUrl);
|
||||
await kernel.model.replace({
|
||||
modelId: '卷帘小',
|
||||
modelUrl: modelUrl,
|
||||
modelControlType: 'color'
|
||||
});
|
||||
console.log(`百叶模型已替换为 ${currentText}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`查询SKU配置或替换模型失败:`, error);
|
||||
}
|
||||
}
|
||||
skuToFunc(currentText);
|
||||
// }
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
document.querySelector('#hotspot-btn').addEventListener('click', async function () {
|
||||
|
||||
await hotspotRequest();
|
||||
})
|
||||
const skuToFunc = async (currentText) => {
|
||||
// 根据 SKU 查询配置和事件
|
||||
try {
|
||||
const response = await fetch(`http://localhost:3000/api/product-configs/by-sku/${currentText}`);
|
||||
const result = await response.json();
|
||||
|
||||
if (result.code === 200 && result.data) {
|
||||
console.log('SKU配置数据:', result.data);
|
||||
console.log('关联事件:', result.data.events);
|
||||
placementWall(1);
|
||||
// 使用 for...of 循环以支持 await
|
||||
for (const event of result.data.events) {
|
||||
if (event.event_type === 'change_model') {
|
||||
const { file_url, model_control_type, category } = event.target_data;
|
||||
console.log('替换百叶模型:', event);
|
||||
|
||||
await kernel.model.replace({
|
||||
modelId: category,
|
||||
modelUrl: file_url,
|
||||
modelControlType: model_control_type,
|
||||
drag: {
|
||||
enable: true,
|
||||
axis: 'x',
|
||||
step: 0.1,
|
||||
},
|
||||
});
|
||||
|
||||
console.log(`百叶模型已替换为 ${currentText}`);
|
||||
}
|
||||
|
||||
if (event.event_type === 'change_color') {
|
||||
const materialName = event.material_name;
|
||||
const { color, color_map_url, normal_map_url } = event.target_data;
|
||||
console.log('替换百叶模型颜色:', event.target_data);
|
||||
|
||||
kernel.material.apply({
|
||||
target: materialName,
|
||||
albedoColor: color,
|
||||
albedoTexture: color_map_url,
|
||||
normalMap: normal_map_url,
|
||||
});
|
||||
|
||||
console.log(`百叶模型颜色已替换为 ${color}`);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.log(`未查询到数据`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`查询SKU配置或替换模型失败:`, error);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
const hotspotRequest = async () => {
|
||||
try {
|
||||
// 从后端获取激活状态的热点列表
|
||||
const response = await fetch('http://localhost:3001/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);
|
||||
}
|
||||
}
|
||||
|
||||
// 监听热点点击事件
|
||||
window.addEventListener('hotspot:click', (event) => {
|
||||
console.log('热点被点击:', event.detail);
|
||||
const { id, name, payload } = event.detail;
|
||||
|
||||
const clickInfoDiv = document.getElementById('click-info');
|
||||
const clickInfoContent = document.getElementById('click-info-content');
|
||||
|
||||
let html = `<div class="click-info-item">
|
||||
<span class="click-info-label">类型:</span>
|
||||
<span class="click-info-value">热点</span>
|
||||
</div>
|
||||
<div class="click-info-item">
|
||||
<span class="click-info-label">名称:</span>
|
||||
<span class="click-info-value">${name}</span>
|
||||
</div>`;
|
||||
|
||||
if (payload && payload.skus && payload.skus.length > 0) {
|
||||
html += `<div class="click-info-item">
|
||||
<span class="click-info-label">关联SKU:</span>
|
||||
<span class="click-info-value">${payload.skus.join(', ')}</span>
|
||||
</div>`;
|
||||
} else {
|
||||
html += `<div class="click-info-item">
|
||||
<span class="click-info-label">关联SKU:</span>
|
||||
<span class="click-info-value">无</span>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
clickInfoContent.innerHTML = html;
|
||||
clickInfoDiv.style.display = 'block';
|
||||
});
|
||||
|
||||
// 监听模型点击事件
|
||||
window.addEventListener('model:click', (event) => {
|
||||
console.log('模型被点击:', event.detail);
|
||||
const { meshName, materialName, modelControlType } = event.detail;
|
||||
|
||||
const clickInfoDiv = document.getElementById('click-info');
|
||||
const clickInfoContent = document.getElementById('click-info-content');
|
||||
|
||||
let html = `<div class="click-info-item">
|
||||
<span class="click-info-label">类型:</span>
|
||||
<span class="click-info-value">模型</span>
|
||||
</div>
|
||||
<div class="click-info-item">
|
||||
<span class="click-info-label">网格名称:</span>
|
||||
<span class="click-info-value">${meshName}</span>
|
||||
</div>`;
|
||||
|
||||
if (materialName) {
|
||||
html += `<div class="click-info-item">
|
||||
<span class="click-info-label">材质名称:</span>
|
||||
<span class="click-info-value">${materialName}</span>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
if (modelControlType) {
|
||||
html += `<div class="click-info-item">
|
||||
<span class="click-info-label">控制类型:</span>
|
||||
<span class="click-info-value">${modelControlType}</span>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
clickInfoContent.innerHTML = html;
|
||||
clickInfoDiv.style.display = 'block';
|
||||
});
|
||||
|
||||
|
||||
// 多选复选框逻辑
|
||||
document.querySelectorAll('.option-checkbox input[type="checkbox"]').forEach(checkbox => {
|
||||
checkbox.addEventListener('change', async function () {
|
||||
@ -545,7 +737,10 @@
|
||||
const materialName = window.getCurrentMaterialName();
|
||||
if (materialName) {
|
||||
console.log('切换为白色,材质名:', materialName);
|
||||
kernel.material.color(materialName, '#FFFFFF');
|
||||
kernel.material.apply({
|
||||
target: materialName,
|
||||
albedoColor: '#FFFFFF',
|
||||
});
|
||||
} else {
|
||||
console.log('没有选中材质');
|
||||
}
|
||||
@ -556,7 +751,10 @@
|
||||
const materialName = window.getCurrentMaterialName();
|
||||
if (materialName) {
|
||||
console.log('切换为黑色,材质名:', materialName);
|
||||
kernel.material.color(materialName, '#000000');
|
||||
kernel.material.apply({
|
||||
target: materialName,
|
||||
albedoColor: '#000000',
|
||||
});
|
||||
} else {
|
||||
console.log('没有选中材质');
|
||||
}
|
||||
@ -617,6 +815,142 @@
|
||||
console.log('没有选中的网格');
|
||||
}
|
||||
});
|
||||
|
||||
// 生成放置区域按钮事件
|
||||
let dropZoneVisible = false;
|
||||
document.getElementById('dropzone-btn').addEventListener('click', () => {
|
||||
if (!dropZoneVisible) {
|
||||
|
||||
|
||||
|
||||
// 更新按钮文字
|
||||
document.getElementById('dropzone-btn').textContent = '隐藏放置区域';
|
||||
console.log('已生成并显示放置区域');
|
||||
} else {
|
||||
// 隐藏放置区域
|
||||
kernel.dropZone.hideAll();
|
||||
dropZoneVisible = false;
|
||||
|
||||
// 更新按钮文字
|
||||
document.getElementById('dropzone-btn').textContent = '生成放置区域';
|
||||
console.log('已隐藏放置区域');
|
||||
}
|
||||
});
|
||||
|
||||
const placementWall = (divisions) => {
|
||||
|
||||
// 先清除旧的放置区域
|
||||
kernel.dropZone.clearAll();
|
||||
|
||||
// 生成新的放置区域(使用新的墙面参数化API)
|
||||
// 调整 baseY 来控制整体高度(正数向上,负数向下)
|
||||
const baseY = 0.08; // 修改这个值来调整整体高度
|
||||
|
||||
// 调整 offset 来控制每个面向外或向内的偏移
|
||||
// 正数 = 向外移动,负数 = 向内移动
|
||||
const wallOffset = -0.07; // 修改这个值来调整墙面偏移
|
||||
|
||||
kernel.dropZone.generate({
|
||||
walls: [
|
||||
{
|
||||
name: 'front',
|
||||
startPoint: [-1.43, baseY, -1.4],
|
||||
endPoint: [1.37, baseY, -1.4],
|
||||
height: 2.2,
|
||||
divisions: divisions,
|
||||
offset: wallOffset // 向外或向内偏移
|
||||
},
|
||||
{
|
||||
name: 'back',
|
||||
startPoint: [1.37, baseY, 1.4],
|
||||
endPoint: [-1.43, baseY, 1.4],
|
||||
height: 2.2,
|
||||
divisions: divisions,
|
||||
offset: wallOffset
|
||||
},
|
||||
{
|
||||
name: 'left',
|
||||
startPoint: [-1.43, baseY, 1.39],
|
||||
endPoint: [-1.43, baseY, -1.43],
|
||||
height: 2.2,
|
||||
divisions: divisions,
|
||||
offset: wallOffset
|
||||
},
|
||||
{
|
||||
name: 'right',
|
||||
startPoint: [1.37, baseY, -1.43],
|
||||
endPoint: [1.37, baseY, 1.4],
|
||||
height: 2.2,
|
||||
divisions: divisions,
|
||||
offset: wallOffset
|
||||
}
|
||||
],
|
||||
color: "#21c7ff",
|
||||
alpha: 0.3,
|
||||
thickness: 2,
|
||||
showBorder: true,
|
||||
borderColor: "#ffffff"
|
||||
});
|
||||
|
||||
// 显示放置区域
|
||||
kernel.dropZone.showAll();
|
||||
dropZoneVisible = true;
|
||||
}
|
||||
|
||||
|
||||
// 监听放置区域点击事件
|
||||
kernel.on('dropzone:click', async (data) => {
|
||||
// 将模型放置到该区域
|
||||
try {
|
||||
const response = await fetch(`http://localhost:3000/api/product-configs/by-sku/${currentText}`);
|
||||
const result = await response.json();
|
||||
|
||||
if (result.code === 200 && result.data) {
|
||||
console.log('SKU配置数据:', result.data);
|
||||
console.log('关联事件:', result.data.events);
|
||||
placementWall(1);
|
||||
// 使用 for...of 循环以支持 await
|
||||
for (const event of result.data.events) {
|
||||
if (event.event_type === 'change_model') {
|
||||
const { file_url, model_control_type, category } = event.target_data;
|
||||
console.log('替换百叶模型:', event);
|
||||
|
||||
await kernel.model.replace({
|
||||
modelId: category,
|
||||
modelUrl: file_url,
|
||||
modelControlType: model_control_type,
|
||||
drag: {
|
||||
enable: true,
|
||||
axis: 'x',
|
||||
step: 0.1,
|
||||
},
|
||||
});
|
||||
|
||||
console.log(`百叶模型已替换为 ${currentText}`);
|
||||
}
|
||||
|
||||
if (event.event_type === 'change_color') {
|
||||
const materialName = event.material_name;
|
||||
const { color, color_map_url, normal_map_url } = event.target_data;
|
||||
console.log('替换百叶模型颜色:', event.target_data);
|
||||
|
||||
kernel.material.apply({
|
||||
target: materialName,
|
||||
albedoColor: color,
|
||||
albedoTexture: color_map_url,
|
||||
normalMap: normal_map_url,
|
||||
});
|
||||
|
||||
console.log(`百叶模型颜色已替换为 ${color}`);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.log(`未查询到数据`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`查询SKU配置或替换模型失败:`, error);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
|
||||
118
index.html
118
index.html
@ -516,58 +516,10 @@
|
||||
|
||||
await hotspotRequest();
|
||||
})
|
||||
let sku = ""
|
||||
const skuToFunc = async (currentText) => {
|
||||
// 根据 SKU 查询配置和事件
|
||||
try {
|
||||
const response = await fetch(`http://localhost:3000/api/product-configs/by-sku/${currentText}`);
|
||||
const result = await response.json();
|
||||
|
||||
if (result.code === 200 && result.data) {
|
||||
console.log('SKU配置数据:', result.data);
|
||||
console.log('关联事件:', result.data.events);
|
||||
placementWall(1);
|
||||
// 使用 for...of 循环以支持 await
|
||||
for (const event of result.data.events) {
|
||||
if (event.event_type === 'change_model') {
|
||||
const { file_url, model_control_type, category } = event.target_data;
|
||||
console.log('替换百叶模型:', event);
|
||||
|
||||
await kernel.model.replace({
|
||||
modelId: category,
|
||||
modelUrl: file_url,
|
||||
modelControlType: model_control_type,
|
||||
drag: {
|
||||
enable: true,
|
||||
axis: 'x',
|
||||
step: 0.1,
|
||||
},
|
||||
});
|
||||
|
||||
console.log(`百叶模型已替换为 ${currentText}`);
|
||||
}
|
||||
|
||||
if (event.event_type === 'change_color') {
|
||||
const materialName = event.material_name;
|
||||
const { color, color_map_url, normal_map_url } = event.target_data;
|
||||
console.log('替换百叶模型颜色:', event.target_data);
|
||||
|
||||
kernel.material.apply({
|
||||
target: materialName,
|
||||
albedoColor: color,
|
||||
albedoTexture: color_map_url,
|
||||
normalMap: normal_map_url,
|
||||
});
|
||||
|
||||
console.log(`百叶模型颜色已替换为 ${color}`);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.log(`未查询到数据`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`查询SKU配置或替换模型失败:`, error);
|
||||
}
|
||||
|
||||
sku = currentText;
|
||||
placementWall(1);
|
||||
|
||||
}
|
||||
|
||||
@ -899,12 +851,64 @@
|
||||
|
||||
|
||||
// 监听放置区域点击事件
|
||||
kernel.on('dropzone:click', (data) => {
|
||||
console.log('点击了放置区域:', data);
|
||||
console.log('中心点:', data.center);
|
||||
console.log('宽度:', data.width);
|
||||
console.log('高度:', data.height);
|
||||
console.log('法线:', data.normal);
|
||||
kernel.on('dropzone:click', async (dropzone_data) => {
|
||||
const { position,rotation } = dropzone_data.transform;
|
||||
|
||||
// 将模型放置到该区域
|
||||
try {
|
||||
const response = await fetch(`http://localhost:3000/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
|
||||
for (const event of result.data.events) {
|
||||
if (event.event_type === 'change_model') {
|
||||
const { file_url, model_control_type, category } = event.target_data;
|
||||
console.log('替换百叶模型:', event);
|
||||
|
||||
await kernel.model.replace({
|
||||
modelId: category,
|
||||
modelUrl: file_url,
|
||||
modelControlType: model_control_type,
|
||||
drag: {
|
||||
enable: true,
|
||||
axis: 'x',
|
||||
step: 0.1,
|
||||
}
|
||||
,
|
||||
transform: {
|
||||
position: position,
|
||||
rotation: rotation,
|
||||
}
|
||||
});
|
||||
|
||||
console.log(`百叶模型已替换为 ${currentText}`);
|
||||
}
|
||||
|
||||
if (event.event_type === 'change_color') {
|
||||
const materialName = event.material_name;
|
||||
const { color, color_map_url, normal_map_url } = event.target_data;
|
||||
console.log('替换百叶模型颜色:', event.target_data);
|
||||
|
||||
kernel.material.apply({
|
||||
target: materialName,
|
||||
albedoColor: color,
|
||||
albedoTexture: color_map_url,
|
||||
normalMap: normal_map_url,
|
||||
});
|
||||
|
||||
console.log(`百叶模型颜色已替换为 ${color}`);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.log(`未查询到数据`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`查询SKU配置或替换模型失败:`, error);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
@ -24,11 +24,18 @@ type ModelConfig = {
|
||||
|
||||
type ModelControlType = 'rotation' | 'color';
|
||||
|
||||
type ModelTransform = {
|
||||
position?: { x: number; y: number; z: number };
|
||||
rotation?: { x: number; y: number; z: number };
|
||||
scale?: { x: number; y: number; z: number };
|
||||
};
|
||||
|
||||
type ModelMetadata = {
|
||||
modelId: string;
|
||||
modelUrl: string;
|
||||
modelControlType?: ModelControlType;
|
||||
drag?: DragConfig;
|
||||
transform?: ModelTransform;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -224,14 +231,15 @@ export class AppModel extends Monobehiver {
|
||||
modelConfig.modelId,
|
||||
modelConfig.modelUrl,
|
||||
modelConfig.modelControlType,
|
||||
modelConfig.drag
|
||||
modelConfig.drag,
|
||||
modelConfig.transform
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加单个模型
|
||||
*/
|
||||
private async addSingle(modelName: string, modelUrl: string, modelControlType?: ModelControlType, drag?: DragConfig): Promise<LoadResult> {
|
||||
private async addSingle(modelName: string, modelUrl: string, modelControlType?: ModelControlType, drag?: DragConfig, transform?: ModelTransform): Promise<LoadResult> {
|
||||
// 检查是否已存在
|
||||
const existingMeshes = this.modelDic.Get(modelName);
|
||||
if (existingMeshes?.length && !existingMeshes[0].isDisposed()) {
|
||||
@ -254,9 +262,15 @@ export class AppModel extends Monobehiver {
|
||||
modelId: modelName,
|
||||
modelUrl: modelUrl,
|
||||
modelControlType: modelControlType,
|
||||
drag: drag
|
||||
drag: drag,
|
||||
transform: transform
|
||||
});
|
||||
|
||||
// 应用 transform
|
||||
if (transform) {
|
||||
this.applyTransform(modelName, transform);
|
||||
}
|
||||
|
||||
// 配置拖拽功能
|
||||
if (drag) {
|
||||
this.mainApp.appModelDrag?.configureDrag(modelName, drag);
|
||||
@ -283,7 +297,7 @@ export class AppModel extends Monobehiver {
|
||||
EventBridge.modelLoadProgress({ loaded: 0, total, progress: 0, percentage: 0 });
|
||||
|
||||
for (let i = 0; i < models.length; i++) {
|
||||
const { modelId, modelUrl, modelControlType, drag } = models[i];
|
||||
const { modelId, modelUrl, modelControlType, drag, transform } = models[i];
|
||||
|
||||
const result = await this.loadSingleModel(modelUrl, (event) => {
|
||||
this.emitProgress(i, total, modelUrl, event);
|
||||
@ -298,9 +312,15 @@ export class AppModel extends Monobehiver {
|
||||
modelId: modelId,
|
||||
modelUrl: modelUrl,
|
||||
modelControlType: modelControlType,
|
||||
drag: drag
|
||||
drag: drag,
|
||||
transform: transform
|
||||
});
|
||||
|
||||
// 应用 transform
|
||||
if (transform) {
|
||||
this.applyTransform(modelId, transform);
|
||||
}
|
||||
|
||||
// 配置拖拽功能
|
||||
if (drag) {
|
||||
this.mainApp.appModelDrag?.configureDrag(modelId, drag);
|
||||
@ -426,7 +446,8 @@ export class AppModel extends Monobehiver {
|
||||
modelConfig.modelId,
|
||||
modelConfig.modelUrl,
|
||||
modelConfig.modelControlType,
|
||||
modelConfig.drag
|
||||
modelConfig.drag,
|
||||
modelConfig.transform
|
||||
);
|
||||
}
|
||||
|
||||
@ -591,4 +612,59 @@ export class AppModel extends Monobehiver {
|
||||
mesh.scaling.z = scale.z;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 将模型放置到指定的放置区域
|
||||
* @param modelId 模型ID
|
||||
* @param zoneInfo 放置区域信息
|
||||
* @param offsetDistance 距离墙面的偏移距离(默认0.1,正数向外)
|
||||
*/
|
||||
placeToZone(modelId: string, zoneInfo: any, offsetDistance: number = 0): void {
|
||||
const meshes = this.modelDic.Get(modelId);
|
||||
if (!meshes?.length) {
|
||||
console.warn(`Model not found: ${modelId}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 计算放置位置:中心点 + 法线方向的偏移
|
||||
const targetPosition = zoneInfo.center.add(zoneInfo.normal.scale(offsetDistance));
|
||||
|
||||
// 计算旋转角度:让模型面向墙面(法线的反方向)
|
||||
const targetDirection = zoneInfo.normal.scale(-1);
|
||||
const angle = Math.atan2(targetDirection.x, targetDirection.z);
|
||||
|
||||
this.getModelTransformTargets(meshes).forEach(mesh => {
|
||||
// 设置位置
|
||||
mesh.position.copyFrom(targetPosition);
|
||||
|
||||
// 设置旋转(只旋转Y轴,让模型面向墙面)
|
||||
if (mesh.rotationQuaternion) {
|
||||
mesh.rotationQuaternion = Quaternion.FromEulerAngles(0, angle, 0);
|
||||
} else {
|
||||
mesh.rotation.set(0, angle, 0);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用 transform 到模型
|
||||
* @param modelId 模型ID
|
||||
* @param transform 变换信息
|
||||
*/
|
||||
private applyTransform(modelId: string, transform: ModelTransform): void {
|
||||
// 应用位置
|
||||
if (transform.position) {
|
||||
this.setPosition(modelId, transform.position);
|
||||
}
|
||||
|
||||
// 应用旋转(角度制)
|
||||
if (transform.rotation) {
|
||||
this.setRotation(modelId, transform.rotation, true);
|
||||
}
|
||||
|
||||
// 应用缩放
|
||||
if (transform.scale) {
|
||||
this.setScale(modelId, transform.scale);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -92,6 +92,12 @@ class AppRay extends Monobehiver {
|
||||
const zones = this.mainApp.appDropZone.getPlacementZones();
|
||||
const clickedZone = zones.find(zone => zone.mesh === pickInfo.pickedMesh);
|
||||
if (clickedZone) {
|
||||
// 计算该放置区域的目标位置和旋转
|
||||
const offsetDistance = 0.1;
|
||||
const targetPosition = clickedZone.center.add(clickedZone.normal.scale(offsetDistance));
|
||||
const targetDirection = clickedZone.normal.scale(-1);
|
||||
const angle = Math.atan2(targetDirection.x, targetDirection.z);
|
||||
|
||||
EventBridge.dropZoneClick({
|
||||
wallName: clickedZone.wallName,
|
||||
index: clickedZone.index,
|
||||
@ -99,7 +105,24 @@ class AppRay extends Monobehiver {
|
||||
width: clickedZone.width,
|
||||
height: clickedZone.height,
|
||||
normal: clickedZone.normal,
|
||||
mesh: clickedZone.mesh
|
||||
mesh: clickedZone.mesh,
|
||||
transform: {
|
||||
position: {
|
||||
x: targetPosition.x,
|
||||
y: targetPosition.y,
|
||||
z: targetPosition.z
|
||||
},
|
||||
rotation: {
|
||||
x: 0,
|
||||
y: angle * 180 / Math.PI, // 转换为角度
|
||||
z: 0
|
||||
},
|
||||
scale: {
|
||||
x: 1,
|
||||
y: 1,
|
||||
z: 1
|
||||
}
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@ -55,4 +55,21 @@ export type DropZoneClickPayload = {
|
||||
height: number;
|
||||
normal: any;
|
||||
mesh: any;
|
||||
transform: {
|
||||
position: {
|
||||
x: number;
|
||||
y: number;
|
||||
z: number;
|
||||
};
|
||||
rotation: {
|
||||
x: number;
|
||||
y: number;
|
||||
z: number;
|
||||
};
|
||||
scale: {
|
||||
x: number;
|
||||
y: number;
|
||||
z: number;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@ -176,6 +176,22 @@ export class KernelAdapter {
|
||||
*/
|
||||
scale: (options: { modelId: string; vector3: { x: number; y: number; z: number } }): void => {
|
||||
this.mainApp.appModel.setScale(options.modelId, options.vector3);
|
||||
},
|
||||
/**
|
||||
* 将模型放置到指定的放置区域
|
||||
* @param options 放置配置 { modelId: string, zoneInfo: any, offsetDistance?: number }
|
||||
* @example
|
||||
* // 监听放置区域点击,将模型放置到该区域
|
||||
* kernel.on('dropzone:click', (zoneInfo) => {
|
||||
* kernel.transform.placeToZone({
|
||||
* modelId: "myModel",
|
||||
* zoneInfo: zoneInfo,
|
||||
* offsetDistance: 0.1 // 可选,距离墙面的偏移距离
|
||||
* });
|
||||
* });
|
||||
*/
|
||||
placeToZone: (options: { modelId: string; zoneInfo: any; offsetDistance?: number }): void => {
|
||||
this.mainApp.appModel.placeToZone(options.modelId, options.zoneInfo, options.offsetDistance);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user