1
This commit is contained in:
BIN
ScreenShot_2026-05-13_110749_413.png
Normal file
BIN
ScreenShot_2026-05-13_110749_413.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 676 KiB |
373
docs/adapter-dropzone-api.md
Normal file
373
docs/adapter-dropzone-api.md
Normal file
@ -0,0 +1,373 @@
|
||||
# 新的 Adapter API 使用指南
|
||||
|
||||
## 放置区域 API 更新
|
||||
|
||||
### 旧的调用方式(已废弃)
|
||||
|
||||
```javascript
|
||||
// 旧方式:基于模型包围盒
|
||||
kernel.dropZone.generate({
|
||||
modelName: "框架",
|
||||
divisions: 4,
|
||||
color: "#21c7ff",
|
||||
alpha: 0.3
|
||||
});
|
||||
|
||||
kernel.dropZone.show("框架");
|
||||
kernel.dropZone.hide("框架");
|
||||
kernel.dropZone.clear("框架");
|
||||
```
|
||||
|
||||
### 新的调用方式
|
||||
|
||||
```javascript
|
||||
// 新方式:直接定义墙面坐标
|
||||
kernel.dropZone.generate({
|
||||
walls: [
|
||||
{
|
||||
name: 'front',
|
||||
startPoint: [-50, 0, -50], // [x, y, z]
|
||||
endPoint: [50, 0, -50],
|
||||
height: 30,
|
||||
divisions: 5
|
||||
},
|
||||
{
|
||||
name: 'back',
|
||||
startPoint: [50, 0, 50],
|
||||
endPoint: [-50, 0, 50],
|
||||
height: 30,
|
||||
divisions: 5
|
||||
},
|
||||
{
|
||||
name: 'left',
|
||||
startPoint: [-50, 0, 50],
|
||||
endPoint: [-50, 0, -50],
|
||||
height: 30,
|
||||
divisions: 4
|
||||
},
|
||||
{
|
||||
name: 'right',
|
||||
startPoint: [50, 0, -50],
|
||||
endPoint: [50, 0, 50],
|
||||
height: 30,
|
||||
divisions: 4
|
||||
}
|
||||
],
|
||||
color: "#21c7ff",
|
||||
alpha: 0.3,
|
||||
thickness: 2,
|
||||
showBorder: true,
|
||||
borderColor: "#ffffff"
|
||||
});
|
||||
|
||||
// 显示/隐藏特定墙面
|
||||
kernel.dropZone.show('front');
|
||||
kernel.dropZone.hide('front');
|
||||
|
||||
// 显示/隐藏所有
|
||||
kernel.dropZone.showAll();
|
||||
kernel.dropZone.hideAll();
|
||||
|
||||
// 清除所有
|
||||
kernel.dropZone.clearAll();
|
||||
```
|
||||
|
||||
## 完整示例
|
||||
|
||||
### 1. 创建矩形放置区域
|
||||
|
||||
```javascript
|
||||
// 定义一个 100x100 的矩形区域
|
||||
const zones = kernel.dropZone.generate({
|
||||
walls: [
|
||||
{
|
||||
name: 'front',
|
||||
startPoint: [-50, 0, -50],
|
||||
endPoint: [50, 0, -50],
|
||||
height: 30,
|
||||
divisions: 10
|
||||
},
|
||||
{
|
||||
name: 'back',
|
||||
startPoint: [50, 0, 50],
|
||||
endPoint: [-50, 0, 50],
|
||||
height: 30,
|
||||
divisions: 10
|
||||
},
|
||||
{
|
||||
name: 'left',
|
||||
startPoint: [-50, 0, 50],
|
||||
endPoint: [-50, 0, -50],
|
||||
height: 30,
|
||||
divisions: 10
|
||||
},
|
||||
{
|
||||
name: 'right',
|
||||
startPoint: [50, 0, -50],
|
||||
endPoint: [50, 0, 50],
|
||||
height: 30,
|
||||
divisions: 10
|
||||
}
|
||||
],
|
||||
color: "#21c7ff",
|
||||
alpha: 0.3
|
||||
});
|
||||
|
||||
console.log(`创建了 ${zones.length} 个放置区域`);
|
||||
```
|
||||
|
||||
### 2. 创建L形放置区域
|
||||
|
||||
```javascript
|
||||
const zones = kernel.dropZone.generate({
|
||||
walls: [
|
||||
{
|
||||
name: 'wall1',
|
||||
startPoint: [0, 0, 0],
|
||||
endPoint: [100, 0, 0],
|
||||
height: 25,
|
||||
divisions: 10
|
||||
},
|
||||
{
|
||||
name: 'wall2',
|
||||
startPoint: [100, 0, 0],
|
||||
endPoint: [100, 0, 60],
|
||||
height: 25,
|
||||
divisions: 6
|
||||
},
|
||||
{
|
||||
name: 'wall3',
|
||||
startPoint: [100, 0, 60],
|
||||
endPoint: [40, 0, 60],
|
||||
height: 25,
|
||||
divisions: 6
|
||||
}
|
||||
],
|
||||
color: "#ff6b6b",
|
||||
alpha: 0.4
|
||||
});
|
||||
```
|
||||
|
||||
### 3. 创建单个展示墙
|
||||
|
||||
```javascript
|
||||
const zones = kernel.dropZone.generate({
|
||||
walls: [
|
||||
{
|
||||
name: 'display',
|
||||
startPoint: [-30, 0, 0],
|
||||
endPoint: [30, 0, 0],
|
||||
height: 20,
|
||||
divisions: 6
|
||||
}
|
||||
],
|
||||
color: "#4ecdc4",
|
||||
alpha: 0.35
|
||||
});
|
||||
```
|
||||
|
||||
### 4. 获取放置区域信息
|
||||
|
||||
```javascript
|
||||
// 获取所有放置区域
|
||||
const allZones = kernel.dropZone.getAll();
|
||||
console.log('总共有', allZones.length, '个放置区域');
|
||||
|
||||
// 获取特定墙面的所有区域
|
||||
const frontZones = kernel.dropZone.getByWall('front');
|
||||
console.log('前墙有', frontZones.length, '个区域');
|
||||
|
||||
// 获取特定的某一块
|
||||
const zone = kernel.dropZone.getZone('front', 2);
|
||||
if (zone) {
|
||||
console.log('前墙第3块:', {
|
||||
center: zone.center,
|
||||
width: zone.width,
|
||||
height: zone.height,
|
||||
normal: zone.normal
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### 5. 检查位置是否在放置区域内
|
||||
|
||||
```javascript
|
||||
const result = kernel.dropZone.checkPosition([10, 15, -50]);
|
||||
|
||||
if (result.inZone) {
|
||||
console.log('在放置区域内:', {
|
||||
墙面: result.wallName,
|
||||
索引: result.index,
|
||||
中心: result.center
|
||||
});
|
||||
} else {
|
||||
console.log('不在任何放置区域内');
|
||||
}
|
||||
```
|
||||
|
||||
### 6. 显示/隐藏操作
|
||||
|
||||
```javascript
|
||||
// 显示所有放置区域
|
||||
kernel.dropZone.showAll();
|
||||
|
||||
// 隐藏所有放置区域
|
||||
kernel.dropZone.hideAll();
|
||||
|
||||
// 显示特定墙面
|
||||
kernel.dropZone.show('front');
|
||||
|
||||
// 隐藏特定墙面
|
||||
kernel.dropZone.hide('back');
|
||||
|
||||
// 清除所有放置区域
|
||||
kernel.dropZone.clearAll();
|
||||
```
|
||||
|
||||
## HTML 按钮示例
|
||||
|
||||
```html
|
||||
<!-- 生成放置区域 -->
|
||||
<button onclick="
|
||||
kernel.dropZone.generate({
|
||||
walls: [
|
||||
{
|
||||
name: 'front',
|
||||
startPoint: [-50, 0, -50],
|
||||
endPoint: [50, 0, -50],
|
||||
height: 30,
|
||||
divisions: 5
|
||||
},
|
||||
{
|
||||
name: 'back',
|
||||
startPoint: [50, 0, 50],
|
||||
endPoint: [-50, 0, 50],
|
||||
height: 30,
|
||||
divisions: 5
|
||||
}
|
||||
],
|
||||
color: '#21c7ff',
|
||||
alpha: 0.3
|
||||
});
|
||||
">生成放置区域</button>
|
||||
|
||||
<!-- 显示所有 -->
|
||||
<button onclick="kernel.dropZone.showAll()">显示所有</button>
|
||||
|
||||
<!-- 隐藏所有 -->
|
||||
<button onclick="kernel.dropZone.hideAll()">隐藏所有</button>
|
||||
|
||||
<!-- 清除所有 -->
|
||||
<button onclick="kernel.dropZone.clearAll()">清除所有</button>
|
||||
|
||||
<!-- 显示前墙 -->
|
||||
<button onclick="kernel.dropZone.show('front')">显示前墙</button>
|
||||
|
||||
<!-- 隐藏前墙 -->
|
||||
<button onclick="kernel.dropZone.hide('front')">隐藏前墙</button>
|
||||
```
|
||||
|
||||
## API 参考
|
||||
|
||||
### `kernel.dropZone.generate(options)`
|
||||
|
||||
生成放置区域
|
||||
|
||||
**参数:**
|
||||
- `walls` - 墙面配置数组
|
||||
- `name` - 墙面名称
|
||||
- `startPoint` - 起始点坐标 `[x, y, z]`
|
||||
- `endPoint` - 结束点坐标 `[x, y, z]`
|
||||
- `height` - 墙面高度
|
||||
- `divisions` - 分割数
|
||||
- `color` - 颜色(可选,默认 `#21c7ff`)
|
||||
- `alpha` - 透明度(可选,默认 `0.3`)
|
||||
- `thickness` - 厚度(可选,默认 `2`)
|
||||
- `showBorder` - 是否显示边框(可选,默认 `false`)
|
||||
- `borderColor` - 边框颜色(可选,默认 `#ffffff`)
|
||||
|
||||
**返回:** 放置区域信息数组
|
||||
|
||||
### `kernel.dropZone.showAll()`
|
||||
|
||||
显示所有放置区域
|
||||
|
||||
### `kernel.dropZone.hideAll()`
|
||||
|
||||
隐藏所有放置区域
|
||||
|
||||
### `kernel.dropZone.show(wallName)`
|
||||
|
||||
显示指定墙面的放置区域
|
||||
|
||||
**参数:**
|
||||
- `wallName` - 墙面名称
|
||||
|
||||
### `kernel.dropZone.hide(wallName)`
|
||||
|
||||
隐藏指定墙面的放置区域
|
||||
|
||||
**参数:**
|
||||
- `wallName` - 墙面名称
|
||||
|
||||
### `kernel.dropZone.clearAll()`
|
||||
|
||||
清除所有放置区域
|
||||
|
||||
### `kernel.dropZone.getAll()`
|
||||
|
||||
获取所有放置区域信息
|
||||
|
||||
**返回:** 放置区域信息数组
|
||||
|
||||
### `kernel.dropZone.getByWall(wallName)`
|
||||
|
||||
获取指定墙面的所有放置区域
|
||||
|
||||
**参数:**
|
||||
- `wallName` - 墙面名称
|
||||
|
||||
**返回:** 放置区域信息数组
|
||||
|
||||
### `kernel.dropZone.getZone(wallName, index)`
|
||||
|
||||
获取特定的放置区域
|
||||
|
||||
**参数:**
|
||||
- `wallName` - 墙面名称
|
||||
- `index` - 区域索引
|
||||
|
||||
**返回:** 放置区域信息对象
|
||||
|
||||
### `kernel.dropZone.checkPosition(position)`
|
||||
|
||||
检查位置是否在放置区域内
|
||||
|
||||
**参数:**
|
||||
- `position` - 位置坐标 `[x, y, z]`
|
||||
|
||||
**返回:** 检查结果对象
|
||||
```javascript
|
||||
{
|
||||
inZone: boolean, // 是否在区域内
|
||||
zone: object, // 区域信息(如果在区域内)
|
||||
wallName: string, // 墙面名称
|
||||
index: number, // 区域索引
|
||||
center: Vector3 // 中心点坐标
|
||||
}
|
||||
```
|
||||
|
||||
## 迁移注意事项
|
||||
|
||||
1. **不再依赖模型名称** - 新API使用墙面名称而不是模型名称
|
||||
2. **坐标需要手动指定** - 不再自动计算包围盒,需要明确指定每个墙面的坐标
|
||||
3. **更灵活的分割** - 每个墙面可以有不同的分割数
|
||||
4. **支持任意形状** - 可以创建L形、U形等不规则形状
|
||||
|
||||
## 优势
|
||||
|
||||
- ✅ 精确控制每个墙面的位置
|
||||
- ✅ 每个墙面可以独立配置
|
||||
- ✅ 支持任意数量的墙面
|
||||
- ✅ 可以创建不规则形状
|
||||
- ✅ 不依赖模型,更灵活
|
||||
407
docs/placement-area-migration.md
Normal file
407
docs/placement-area-migration.md
Normal file
@ -0,0 +1,407 @@
|
||||
# 放置区域 API 迁移指南
|
||||
|
||||
## 概述
|
||||
|
||||
新的放置区域系统使用**参数化墙面定义**替代了旧的**包围盒自动生成**方案,提供更灵活、更精确的控制。
|
||||
|
||||
---
|
||||
|
||||
## 主要变化
|
||||
|
||||
### 旧方案的问题
|
||||
- ❌ 依赖模型包围盒自动计算
|
||||
- ❌ 只能生成固定的矩形四周
|
||||
- ❌ 无法适应不规则形状
|
||||
- ❌ 所有墙面必须使用相同的分割数
|
||||
|
||||
### 新方案的优势
|
||||
- ✅ 直接定义每个墙面的起始和结束坐标
|
||||
- ✅ 每个墙面可以有独立的分割数
|
||||
- ✅ 支持任意数量的墙面(1个、4个、N个)
|
||||
- ✅ 可以创建不规则形状(L形、U形等)
|
||||
- ✅ 不依赖模型,坐标完全可控
|
||||
|
||||
---
|
||||
|
||||
## API 对比
|
||||
|
||||
### 旧 API(已废弃)
|
||||
|
||||
```typescript
|
||||
import { AppDropZone } from './AppDropZone';
|
||||
|
||||
const appDropZone = new AppDropZone(scene);
|
||||
|
||||
// 旧的配置方式
|
||||
const zones = appDropZone.generateDropZones({
|
||||
modelName: 'myModel', // 依赖模型名称
|
||||
divisions: 5, // 所有边使用相同分割数
|
||||
color: '#21c7ff',
|
||||
alpha: 0.3,
|
||||
thickness: 2,
|
||||
offset: 5, // 距离模型的偏移
|
||||
scale: 1.0 // 缩放比例
|
||||
});
|
||||
|
||||
// 返回 Mesh[]
|
||||
```
|
||||
|
||||
### 新 API(推荐)
|
||||
|
||||
```typescript
|
||||
import { AppDropZone } from './AppDropZone';
|
||||
|
||||
const appDropZone = new AppDropZone(scene);
|
||||
|
||||
// 新的配置方式
|
||||
const zones = appDropZone.generateDropZones({
|
||||
walls: [ // 墙面数组
|
||||
{
|
||||
name: 'front', // 墙面名称
|
||||
startPoint: new Vector3(-50, 0, -50), // 起始点
|
||||
endPoint: new Vector3(50, 0, -50), // 结束点
|
||||
height: 30, // 高度
|
||||
divisions: 5 // 这个墙面的分割数
|
||||
},
|
||||
{
|
||||
name: 'back',
|
||||
startPoint: new Vector3(50, 0, 50),
|
||||
endPoint: new Vector3(-50, 0, 50),
|
||||
height: 30,
|
||||
divisions: 5
|
||||
},
|
||||
// ... 更多墙面
|
||||
],
|
||||
color: '#21c7ff',
|
||||
alpha: 0.3,
|
||||
thickness: 2,
|
||||
showBorder: true, // 是否显示边框
|
||||
borderColor: '#ffffff' // 边框颜色
|
||||
});
|
||||
|
||||
// 返回 PlacementZoneInfo[]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 迁移步骤
|
||||
|
||||
### 步骤 1:确定墙面坐标
|
||||
|
||||
如果你之前使用模型包围盒,需要先获取模型的边界坐标:
|
||||
|
||||
```typescript
|
||||
// 获取模型的包围盒坐标
|
||||
function getModelBounds(scene: Scene, modelName: string) {
|
||||
const mesh = scene.getMeshByName(modelName);
|
||||
if (!mesh) return null;
|
||||
|
||||
mesh.computeWorldMatrix(true);
|
||||
const boundingInfo = mesh.getBoundingInfo();
|
||||
const min = boundingInfo.boundingBox.minimumWorld;
|
||||
const max = boundingInfo.boundingBox.maximumWorld;
|
||||
|
||||
return {
|
||||
minX: min.x,
|
||||
maxX: max.x,
|
||||
minY: min.y,
|
||||
maxY: max.y,
|
||||
minZ: min.z,
|
||||
maxZ: max.z
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### 步骤 2:转换为墙面配置
|
||||
|
||||
```typescript
|
||||
const bounds = getModelBounds(scene, 'myModel');
|
||||
if (!bounds) return;
|
||||
|
||||
const { minX, maxX, minY, maxY, minZ, maxZ } = bounds;
|
||||
const height = maxY - minY;
|
||||
|
||||
// 转换为新的墙面配置
|
||||
const walls = [
|
||||
{
|
||||
name: 'front',
|
||||
startPoint: new Vector3(minX, minY, minZ),
|
||||
endPoint: new Vector3(maxX, minY, minZ),
|
||||
height: height,
|
||||
divisions: 5
|
||||
},
|
||||
{
|
||||
name: 'back',
|
||||
startPoint: new Vector3(maxX, minY, maxZ),
|
||||
endPoint: new Vector3(minX, minY, maxZ),
|
||||
height: height,
|
||||
divisions: 5
|
||||
},
|
||||
{
|
||||
name: 'left',
|
||||
startPoint: new Vector3(minX, minY, maxZ),
|
||||
endPoint: new Vector3(minX, minY, minZ),
|
||||
height: height,
|
||||
divisions: 5
|
||||
},
|
||||
{
|
||||
name: 'right',
|
||||
startPoint: new Vector3(maxX, minY, minZ),
|
||||
endPoint: new Vector3(maxX, minY, maxZ),
|
||||
height: height,
|
||||
divisions: 5
|
||||
}
|
||||
];
|
||||
```
|
||||
|
||||
### 步骤 3:应用偏移(如果需要)
|
||||
|
||||
如果旧代码中使用了 `offset` 参数,需要手动调整坐标:
|
||||
|
||||
```typescript
|
||||
const offset = 5;
|
||||
|
||||
// 前墙向外偏移
|
||||
walls[0].startPoint.z -= offset;
|
||||
walls[0].endPoint.z -= offset;
|
||||
|
||||
// 后墙向外偏移
|
||||
walls[1].startPoint.z += offset;
|
||||
walls[1].endPoint.z += offset;
|
||||
|
||||
// 左墙向外偏移
|
||||
walls[2].startPoint.x -= offset;
|
||||
walls[2].endPoint.x -= offset;
|
||||
|
||||
// 右墙向外偏移
|
||||
walls[3].startPoint.x += offset;
|
||||
walls[3].endPoint.x += offset;
|
||||
```
|
||||
|
||||
### 步骤 4:应用缩放(如果需要)
|
||||
|
||||
如果旧代码中使用了 `scale` 参数,需要调整坐标:
|
||||
|
||||
```typescript
|
||||
const scale = 0.8; // 缩放到80%
|
||||
|
||||
const centerX = (minX + maxX) / 2;
|
||||
const centerZ = (minZ + maxZ) / 2;
|
||||
|
||||
const scaledWidth = (maxX - minX) * scale;
|
||||
const scaledDepth = (maxZ - minZ) * scale;
|
||||
|
||||
const scaledMinX = centerX - scaledWidth / 2;
|
||||
const scaledMaxX = centerX + scaledWidth / 2;
|
||||
const scaledMinZ = centerZ - scaledDepth / 2;
|
||||
const scaledMaxZ = centerZ + scaledDepth / 2;
|
||||
|
||||
// 使用缩放后的坐标
|
||||
walls[0].startPoint = new Vector3(scaledMinX, minY, scaledMinZ);
|
||||
walls[0].endPoint = new Vector3(scaledMaxX, minY, scaledMinZ);
|
||||
// ... 其他墙面类似
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 完整迁移示例
|
||||
|
||||
### 旧代码
|
||||
|
||||
```typescript
|
||||
const appDropZone = new AppDropZone(scene);
|
||||
|
||||
const zones = appDropZone.generateDropZones({
|
||||
modelName: 'warehouse',
|
||||
divisions: 10,
|
||||
color: '#21c7ff',
|
||||
alpha: 0.3,
|
||||
thickness: 2,
|
||||
offset: 5,
|
||||
scale: 0.9
|
||||
});
|
||||
```
|
||||
|
||||
### 新代码
|
||||
|
||||
```typescript
|
||||
const appDropZone = new AppDropZone(scene);
|
||||
|
||||
// 1. 获取模型边界
|
||||
const bounds = getModelBounds(scene, 'warehouse');
|
||||
if (!bounds) return;
|
||||
|
||||
const { minX, maxX, minY, maxY, minZ, maxZ } = bounds;
|
||||
|
||||
// 2. 应用缩放
|
||||
const scale = 0.9;
|
||||
const centerX = (minX + maxX) / 2;
|
||||
const centerZ = (minZ + maxZ) / 2;
|
||||
const width = (maxX - minX) * scale;
|
||||
const depth = (maxZ - minZ) * scale;
|
||||
const height = maxY - minY;
|
||||
|
||||
const scaledMinX = centerX - width / 2;
|
||||
const scaledMaxX = centerX + width / 2;
|
||||
const scaledMinZ = centerZ - depth / 2;
|
||||
const scaledMaxZ = centerZ + depth / 2;
|
||||
|
||||
// 3. 应用偏移
|
||||
const offset = 5;
|
||||
|
||||
// 4. 生成放置区域
|
||||
const zones = appDropZone.generateDropZones({
|
||||
walls: [
|
||||
{
|
||||
name: 'front',
|
||||
startPoint: new Vector3(scaledMinX, minY, scaledMinZ - offset),
|
||||
endPoint: new Vector3(scaledMaxX, minY, scaledMinZ - offset),
|
||||
height: height,
|
||||
divisions: 10
|
||||
},
|
||||
{
|
||||
name: 'back',
|
||||
startPoint: new Vector3(scaledMaxX, minY, scaledMaxZ + offset),
|
||||
endPoint: new Vector3(scaledMinX, minY, scaledMaxZ + offset),
|
||||
height: height,
|
||||
divisions: 10
|
||||
},
|
||||
{
|
||||
name: 'left',
|
||||
startPoint: new Vector3(scaledMinX - offset, minY, scaledMaxZ),
|
||||
endPoint: new Vector3(scaledMinX - offset, minY, scaledMinZ),
|
||||
height: height,
|
||||
divisions: 10
|
||||
},
|
||||
{
|
||||
name: 'right',
|
||||
startPoint: new Vector3(scaledMaxX + offset, minY, scaledMinZ),
|
||||
endPoint: new Vector3(scaledMaxX + offset, minY, scaledMaxZ),
|
||||
height: height,
|
||||
divisions: 10
|
||||
}
|
||||
],
|
||||
color: '#21c7ff',
|
||||
alpha: 0.3,
|
||||
thickness: 2,
|
||||
showBorder: true
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 新功能示例
|
||||
|
||||
### 1. 不同墙面使用不同分割数
|
||||
|
||||
```typescript
|
||||
const zones = appDropZone.generateDropZones({
|
||||
walls: [
|
||||
{ name: 'front', ..., divisions: 10 }, // 前墙10块
|
||||
{ name: 'back', ..., divisions: 10 }, // 后墙10块
|
||||
{ name: 'left', ..., divisions: 5 }, // 左墙5块
|
||||
{ name: 'right', ..., divisions: 5 } // 右墙5块
|
||||
],
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
### 2. 创建L形区域
|
||||
|
||||
```typescript
|
||||
const zones = appDropZone.generateDropZones({
|
||||
walls: [
|
||||
{
|
||||
name: 'wall1',
|
||||
startPoint: new Vector3(0, 0, 0),
|
||||
endPoint: new Vector3(100, 0, 0),
|
||||
height: 25,
|
||||
divisions: 10
|
||||
},
|
||||
{
|
||||
name: 'wall2',
|
||||
startPoint: new Vector3(100, 0, 0),
|
||||
endPoint: new Vector3(100, 0, 60),
|
||||
height: 25,
|
||||
divisions: 6
|
||||
}
|
||||
],
|
||||
color: '#ff6b6b',
|
||||
alpha: 0.4
|
||||
});
|
||||
```
|
||||
|
||||
### 3. 只创建单个墙面
|
||||
|
||||
```typescript
|
||||
const zones = appDropZone.generateDropZones({
|
||||
walls: [
|
||||
{
|
||||
name: 'display',
|
||||
startPoint: new Vector3(-30, 0, 0),
|
||||
endPoint: new Vector3(30, 0, 0),
|
||||
height: 20,
|
||||
divisions: 6
|
||||
}
|
||||
],
|
||||
color: '#4ecdc4',
|
||||
alpha: 0.35
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 获取放置区域信息
|
||||
|
||||
新API返回更详细的区域信息:
|
||||
|
||||
```typescript
|
||||
const zones = appDropZone.generateDropZones(config);
|
||||
|
||||
// 每个zone包含:
|
||||
zones.forEach(zone => {
|
||||
console.log({
|
||||
mesh: zone.mesh, // Babylon.js Mesh对象
|
||||
wallName: zone.wallName, // 所属墙面名称
|
||||
index: zone.index, // 在该墙面上的索引
|
||||
center: zone.center, // 中心点坐标
|
||||
width: zone.width, // 宽度
|
||||
height: zone.height, // 高度
|
||||
normal: zone.normal // 法线方向
|
||||
});
|
||||
});
|
||||
|
||||
// 获取特定墙面的所有区域
|
||||
const frontZones = appDropZone.getZonesByWall('front');
|
||||
|
||||
// 获取特定的某一块
|
||||
const zone = appDropZone.getZone('front', 2);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q: 我必须迁移吗?
|
||||
A: 旧的API已经被完全移除,必须迁移到新API。
|
||||
|
||||
### Q: 如何快速迁移?
|
||||
A: 使用上面的 `getModelBounds` 函数获取模型边界,然后转换为墙面配置。
|
||||
|
||||
### Q: 新API性能如何?
|
||||
A: 新API性能更好,因为不需要计算包围盒,直接使用预定义的坐标。
|
||||
|
||||
### Q: 可以动态调整墙面吗?
|
||||
A: 可以,调用 `clearAll()` 清除旧的,然后用新坐标重新生成。
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
|
||||
新的放置区域系统提供了:
|
||||
- ✅ 更精确的控制
|
||||
- ✅ 更灵活的配置
|
||||
- ✅ 更好的性能
|
||||
- ✅ 更清晰的API
|
||||
|
||||
虽然迁移需要一些工作,但新系统的灵活性和可控性值得这个投入!
|
||||
233
examples/drop-zone-usage.ts
Normal file
233
examples/drop-zone-usage.ts
Normal file
@ -0,0 +1,233 @@
|
||||
import { Scene, Vector3 } from '@babylonjs/core';
|
||||
import { AppDropZone } from '../src/babylonjs/AppDropZone';
|
||||
|
||||
/**
|
||||
* 使用示例:创建矩形放置区域
|
||||
*
|
||||
* 旧的API(已废弃):
|
||||
* appDropZone.generateDropZones({
|
||||
* modelName: 'myModel',
|
||||
* divisions: 5,
|
||||
* color: '#21c7ff',
|
||||
* alpha: 0.3
|
||||
* });
|
||||
*
|
||||
* 新的API:直接定义墙面坐标和分割数
|
||||
*/
|
||||
export function createDropZonesExample(scene: Scene) {
|
||||
const appDropZone = new AppDropZone(scene);
|
||||
|
||||
// 定义一个矩形区域的四个墙面
|
||||
const zones = appDropZone.generateDropZones({
|
||||
walls: [
|
||||
{
|
||||
name: 'front',
|
||||
startPoint: new Vector3(-50, 0, -50), // 前墙左下角
|
||||
endPoint: new Vector3(50, 0, -50), // 前墙右下角
|
||||
height: 30,
|
||||
divisions: 5 // 前墙分成5块
|
||||
},
|
||||
{
|
||||
name: 'back',
|
||||
startPoint: new Vector3(50, 0, 50), // 后墙右下角
|
||||
endPoint: new Vector3(-50, 0, 50), // 后墙左下角
|
||||
height: 30,
|
||||
divisions: 5 // 后墙分成5块
|
||||
},
|
||||
{
|
||||
name: 'left',
|
||||
startPoint: new Vector3(-50, 0, 50), // 左墙后下角
|
||||
endPoint: new Vector3(-50, 0, -50), // 左墙前下角
|
||||
height: 30,
|
||||
divisions: 4 // 左墙分成4块
|
||||
},
|
||||
{
|
||||
name: 'right',
|
||||
startPoint: new Vector3(50, 0, -50), // 右墙前下角
|
||||
endPoint: new Vector3(50, 0, 50), // 右墙后下角
|
||||
height: 30,
|
||||
divisions: 4 // 右墙分成4块
|
||||
}
|
||||
],
|
||||
color: '#21c7ff',
|
||||
alpha: 0.3,
|
||||
thickness: 2,
|
||||
showBorder: true,
|
||||
borderColor: '#ffffff'
|
||||
});
|
||||
|
||||
console.log(`创建了 ${zones.length} 个放置区域`);
|
||||
|
||||
// 获取特定区域
|
||||
const frontZones = appDropZone.getZonesByWall('front');
|
||||
console.log(`前墙有 ${frontZones.length} 个区域`);
|
||||
|
||||
// 获取某一块
|
||||
const zone = appDropZone.getZone('front', 2);
|
||||
if (zone) {
|
||||
console.log('前墙第3块:', {
|
||||
center: zone.center,
|
||||
width: zone.width,
|
||||
height: zone.height,
|
||||
normal: zone.normal
|
||||
});
|
||||
}
|
||||
|
||||
return appDropZone;
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用示例:创建L形放置区域
|
||||
*/
|
||||
export function createLShapeDropZones(scene: Scene) {
|
||||
const appDropZone = new AppDropZone(scene);
|
||||
|
||||
const zones = appDropZone.generateDropZones({
|
||||
walls: [
|
||||
{
|
||||
name: 'wall1',
|
||||
startPoint: new Vector3(0, 0, 0),
|
||||
endPoint: new Vector3(100, 0, 0),
|
||||
height: 25,
|
||||
divisions: 10
|
||||
},
|
||||
{
|
||||
name: 'wall2',
|
||||
startPoint: new Vector3(100, 0, 0),
|
||||
endPoint: new Vector3(100, 0, 60),
|
||||
height: 25,
|
||||
divisions: 6
|
||||
},
|
||||
{
|
||||
name: 'wall3',
|
||||
startPoint: new Vector3(100, 0, 60),
|
||||
endPoint: new Vector3(40, 0, 60),
|
||||
height: 25,
|
||||
divisions: 6
|
||||
}
|
||||
],
|
||||
color: '#ff6b6b',
|
||||
alpha: 0.4,
|
||||
showBorder: true
|
||||
});
|
||||
|
||||
return appDropZone;
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用示例:创建单个展示墙
|
||||
*/
|
||||
export function createDisplayWall(scene: Scene) {
|
||||
const appDropZone = new AppDropZone(scene);
|
||||
|
||||
const zones = appDropZone.generateDropZones({
|
||||
walls: [
|
||||
{
|
||||
name: 'display',
|
||||
startPoint: new Vector3(-30, 0, 0),
|
||||
endPoint: new Vector3(30, 0, 0),
|
||||
height: 20,
|
||||
divisions: 6
|
||||
}
|
||||
],
|
||||
color: '#4ecdc4',
|
||||
alpha: 0.35,
|
||||
thickness: 1.5
|
||||
});
|
||||
|
||||
return appDropZone;
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用示例:根据实际模型坐标创建
|
||||
* 假设你已经知道模型的边界坐标
|
||||
*/
|
||||
export function createDropZonesFromModelBounds(
|
||||
scene: Scene,
|
||||
minX: number,
|
||||
maxX: number,
|
||||
minY: number,
|
||||
maxY: number,
|
||||
minZ: number,
|
||||
maxZ: number,
|
||||
divisions: number = 5
|
||||
) {
|
||||
const appDropZone = new AppDropZone(scene);
|
||||
|
||||
const height = maxY - minY;
|
||||
|
||||
const zones = appDropZone.generateDropZones({
|
||||
walls: [
|
||||
// 前墙
|
||||
{
|
||||
name: 'front',
|
||||
startPoint: new Vector3(minX, minY, minZ),
|
||||
endPoint: new Vector3(maxX, minY, minZ),
|
||||
height: height,
|
||||
divisions: divisions
|
||||
},
|
||||
// 后墙
|
||||
{
|
||||
name: 'back',
|
||||
startPoint: new Vector3(maxX, minY, maxZ),
|
||||
endPoint: new Vector3(minX, minY, maxZ),
|
||||
height: height,
|
||||
divisions: divisions
|
||||
},
|
||||
// 左墙
|
||||
{
|
||||
name: 'left',
|
||||
startPoint: new Vector3(minX, minY, maxZ),
|
||||
endPoint: new Vector3(minX, minY, minZ),
|
||||
height: height,
|
||||
divisions: divisions
|
||||
},
|
||||
// 右墙
|
||||
{
|
||||
name: 'right',
|
||||
startPoint: new Vector3(maxX, minY, minZ),
|
||||
endPoint: new Vector3(maxX, minY, maxZ),
|
||||
height: height,
|
||||
divisions: divisions
|
||||
}
|
||||
],
|
||||
color: '#21c7ff',
|
||||
alpha: 0.3,
|
||||
thickness: 2,
|
||||
showBorder: true
|
||||
});
|
||||
|
||||
return appDropZone;
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用示例:操作放置区域
|
||||
*/
|
||||
export function manipulateDropZones(appDropZone: AppDropZone) {
|
||||
// 获取所有区域
|
||||
const allZones = appDropZone.getPlacementZones();
|
||||
console.log('总共有', allZones.length, '个放置区域');
|
||||
|
||||
// 遍历所有区域
|
||||
allZones.forEach((zone, index) => {
|
||||
console.log(`区域 ${index}:`, {
|
||||
墙面: zone.wallName,
|
||||
索引: zone.index,
|
||||
中心: zone.center,
|
||||
尺寸: `${zone.width} x ${zone.height}`,
|
||||
法线: zone.normal
|
||||
});
|
||||
});
|
||||
|
||||
// 隐藏所有区域
|
||||
appDropZone.hide();
|
||||
|
||||
// 显示所有区域
|
||||
appDropZone.show();
|
||||
|
||||
// 清除所有区域
|
||||
// appDropZone.clearAll();
|
||||
|
||||
// 销毁
|
||||
// appDropZone.dispose();
|
||||
}
|
||||
215
examples/placement-wall-example.ts
Normal file
215
examples/placement-wall-example.ts
Normal file
@ -0,0 +1,215 @@
|
||||
import { Scene, Vector3 } from '@babylonjs/core';
|
||||
import { AppPlacementWall } from '../src/babylonjs/AppPlacementWall';
|
||||
|
||||
/**
|
||||
* 使用示例:创建一个矩形围栏的放置区域
|
||||
*/
|
||||
export function createRectangularWalls(scene: Scene) {
|
||||
const placementWall = new AppPlacementWall(scene);
|
||||
|
||||
// 定义一个矩形区域的四个墙面
|
||||
const config = {
|
||||
walls: [
|
||||
{
|
||||
name: 'front',
|
||||
startPoint: new Vector3(-50, 0, -50), // 前墙左下角
|
||||
endPoint: new Vector3(50, 0, -50), // 前墙右下角
|
||||
height: 30,
|
||||
divisions: 5 // 前墙分成5块
|
||||
},
|
||||
{
|
||||
name: 'back',
|
||||
startPoint: new Vector3(50, 0, 50), // 后墙右下角
|
||||
endPoint: new Vector3(-50, 0, 50), // 后墙左下角
|
||||
height: 30,
|
||||
divisions: 5 // 后墙分成5块
|
||||
},
|
||||
{
|
||||
name: 'left',
|
||||
startPoint: new Vector3(-50, 0, 50), // 左墙后下角
|
||||
endPoint: new Vector3(-50, 0, -50), // 左墙前下角
|
||||
height: 30,
|
||||
divisions: 4 // 左墙分成4块
|
||||
},
|
||||
{
|
||||
name: 'right',
|
||||
startPoint: new Vector3(50, 0, -50), // 右墙前下角
|
||||
endPoint: new Vector3(50, 0, 50), // 右墙后下角
|
||||
height: 30,
|
||||
divisions: 4 // 右墙分成4块
|
||||
}
|
||||
],
|
||||
color: '#21c7ff',
|
||||
alpha: 0.3,
|
||||
thickness: 2,
|
||||
showBorder: true,
|
||||
borderColor: '#ffffff'
|
||||
};
|
||||
|
||||
const zones = placementWall.generatePlacementAreas(config);
|
||||
console.log(`创建了 ${zones.length} 个放置区域`);
|
||||
|
||||
return placementWall;
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用示例:创建不规则形状的墙面
|
||||
*/
|
||||
export function createIrregularWalls(scene: Scene) {
|
||||
const placementWall = new AppPlacementWall(scene);
|
||||
|
||||
// 定义一个L形区域
|
||||
const config = {
|
||||
walls: [
|
||||
{
|
||||
name: 'wall1',
|
||||
startPoint: new Vector3(0, 0, 0),
|
||||
endPoint: new Vector3(100, 0, 0),
|
||||
height: 25,
|
||||
divisions: 10
|
||||
},
|
||||
{
|
||||
name: 'wall2',
|
||||
startPoint: new Vector3(100, 0, 0),
|
||||
endPoint: new Vector3(100, 0, 60),
|
||||
height: 25,
|
||||
divisions: 6
|
||||
},
|
||||
{
|
||||
name: 'wall3',
|
||||
startPoint: new Vector3(100, 0, 60),
|
||||
endPoint: new Vector3(40, 0, 60),
|
||||
height: 25,
|
||||
divisions: 6
|
||||
}
|
||||
],
|
||||
color: '#ff6b6b',
|
||||
alpha: 0.4,
|
||||
showBorder: true
|
||||
};
|
||||
|
||||
return placementWall.generatePlacementAreas(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用示例:创建单个墙面
|
||||
*/
|
||||
export function createSingleWall(scene: Scene) {
|
||||
const placementWall = new AppPlacementWall(scene);
|
||||
|
||||
const config = {
|
||||
walls: [
|
||||
{
|
||||
name: 'display_wall',
|
||||
startPoint: new Vector3(-30, 0, 0),
|
||||
endPoint: new Vector3(30, 0, 0),
|
||||
height: 20,
|
||||
divisions: 6 // 分成6个展示区域
|
||||
}
|
||||
],
|
||||
color: '#4ecdc4',
|
||||
alpha: 0.35,
|
||||
thickness: 1.5
|
||||
};
|
||||
|
||||
return placementWall.generatePlacementAreas(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用示例:获取特定放置区域并操作
|
||||
*/
|
||||
export function interactWithZones(placementWall: AppPlacementWall) {
|
||||
// 获取所有放置区域
|
||||
const allZones = placementWall.getPlacementZones();
|
||||
console.log('总共有', allZones.length, '个放置区域');
|
||||
|
||||
// 获取特定墙面的所有区域
|
||||
const frontZones = placementWall.getZonesByWall('front');
|
||||
console.log('前墙有', frontZones.length, '个区域');
|
||||
|
||||
// 获取特定的某一块
|
||||
const zone = placementWall.getZone('front', 2);
|
||||
if (zone) {
|
||||
console.log('前墙第3块的中心点:', zone.center);
|
||||
console.log('尺寸:', zone.width, 'x', zone.height);
|
||||
console.log('法线方向:', zone.normal);
|
||||
|
||||
// 可以对这个区域做特殊处理
|
||||
// 比如改变颜色、添加标签等
|
||||
}
|
||||
|
||||
// 隐藏所有区域
|
||||
// placementWall.hide();
|
||||
|
||||
// 显示所有区域
|
||||
// placementWall.show();
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用示例:根据实际场景坐标创建
|
||||
* 假设你有一个仓库模型,想在其四周创建货架放置区域
|
||||
*/
|
||||
export function createWarehouseShelfAreas(scene: Scene) {
|
||||
const placementWall = new AppPlacementWall(scene);
|
||||
|
||||
// 假设仓库的实际坐标
|
||||
const warehouseCorners = {
|
||||
frontLeft: new Vector3(-100, 0, -80),
|
||||
frontRight: new Vector3(100, 0, -80),
|
||||
backLeft: new Vector3(-100, 0, 80),
|
||||
backRight: new Vector3(100, 0, 80)
|
||||
};
|
||||
|
||||
const shelfHeight = 40;
|
||||
|
||||
const config = {
|
||||
walls: [
|
||||
// 前墙 - 分成20个货架位
|
||||
{
|
||||
name: 'front_shelf',
|
||||
startPoint: warehouseCorners.frontLeft,
|
||||
endPoint: warehouseCorners.frontRight,
|
||||
height: shelfHeight,
|
||||
divisions: 20
|
||||
},
|
||||
// 后墙 - 分成20个货架位
|
||||
{
|
||||
name: 'back_shelf',
|
||||
startPoint: warehouseCorners.backRight,
|
||||
endPoint: warehouseCorners.backLeft,
|
||||
height: shelfHeight,
|
||||
divisions: 20
|
||||
},
|
||||
// 左墙 - 分成16个货架位
|
||||
{
|
||||
name: 'left_shelf',
|
||||
startPoint: warehouseCorners.backLeft,
|
||||
endPoint: warehouseCorners.frontLeft,
|
||||
height: shelfHeight,
|
||||
divisions: 16
|
||||
},
|
||||
// 右墙 - 分成16个货架位
|
||||
{
|
||||
name: 'right_shelf',
|
||||
startPoint: warehouseCorners.frontRight,
|
||||
endPoint: warehouseCorners.backRight,
|
||||
height: shelfHeight,
|
||||
divisions: 16
|
||||
}
|
||||
],
|
||||
color: '#ffd93d',
|
||||
alpha: 0.25,
|
||||
thickness: 3,
|
||||
showBorder: true,
|
||||
borderColor: '#ff6b35'
|
||||
};
|
||||
|
||||
const zones = placementWall.generatePlacementAreas(config);
|
||||
|
||||
// 为每个货架区域添加编号
|
||||
zones.forEach((zone, index) => {
|
||||
console.log(`货架 ${zone.wallName}-${zone.index}: 位置 ${zone.center}`);
|
||||
});
|
||||
|
||||
return placementWall;
|
||||
}
|
||||
41
index.html
41
index.html
@ -821,21 +821,46 @@
|
||||
let dropZoneVisible = false;
|
||||
document.getElementById('dropzone-btn').addEventListener('click', () => {
|
||||
if (!dropZoneVisible) {
|
||||
// 生成放置区域(假设有一个名为"框架"的模型)
|
||||
const modelName = "pergola_80s_3x3"; // 可以改成你实际的模型名称
|
||||
|
||||
// 先清除旧的放置区域
|
||||
kernel.dropZone.clearAll();
|
||||
|
||||
// 生成新的放置区域
|
||||
// 生成新的放置区域(使用新的墙面参数化API)
|
||||
kernel.dropZone.generate({
|
||||
modelName: modelName,
|
||||
divisions: 4,
|
||||
walls: [
|
||||
{
|
||||
name: 'front',
|
||||
startPoint: [-1.4, 0, -1.4],
|
||||
endPoint: [1.4, 0, -1.4],
|
||||
height: 2.3,
|
||||
divisions: 5
|
||||
},
|
||||
{
|
||||
name: 'back',
|
||||
startPoint: [50, 0, 50],
|
||||
endPoint: [-50, 0, 50],
|
||||
height: 30,
|
||||
divisions: 5
|
||||
},
|
||||
{
|
||||
name: 'left',
|
||||
startPoint: [-50, 0, 50],
|
||||
endPoint: [-50, 0, -50],
|
||||
height: 30,
|
||||
divisions: 4
|
||||
},
|
||||
{
|
||||
name: 'right',
|
||||
startPoint: [50, 0, -50],
|
||||
endPoint: [50, 0, 50],
|
||||
height: 30,
|
||||
divisions: 4
|
||||
}
|
||||
],
|
||||
color: "#21c7ff",
|
||||
alpha: 0.3,
|
||||
thickness: 2,
|
||||
offset: 0, // 改小偏移量,从 5 改为 0.5
|
||||
scale: 0.9 // 缩小到80%,生成内部放置区域
|
||||
showBorder: true,
|
||||
borderColor: "#ffffff"
|
||||
});
|
||||
|
||||
// 显示放置区域
|
||||
|
||||
@ -1,540 +1,84 @@
|
||||
import { Scene, Mesh, MeshBuilder, StandardMaterial, Color3, Vector3, AbstractMesh, BoundingBoxGizmo } from '@babylonjs/core';
|
||||
import { Scene, Vector3 } from '@babylonjs/core';
|
||||
import { AppPlacementWall, WallConfig, PlacementZoneInfo } from './AppPlacementWall';
|
||||
|
||||
/**
|
||||
* 放置区域配置
|
||||
*/
|
||||
export interface DropZoneConfig {
|
||||
modelName: string; // 目标模型名称
|
||||
divisions: number; // 分割块数(每条边分成几块)
|
||||
walls: WallConfig[]; // 墙面配置数组
|
||||
color?: string; // 颜色(十六进制)
|
||||
alpha?: number; // 透明度
|
||||
thickness?: number; // 厚度
|
||||
offset?: number; // 距离模型的偏移量
|
||||
scale?: number; // 整体缩放比例(0-1),用于生成内部放置区域
|
||||
showBorder?: boolean; // 是否显示边框
|
||||
borderColor?: string; // 边框颜色
|
||||
}
|
||||
|
||||
/**
|
||||
* 放置区域管理类(使用新的墙面参数化方案)
|
||||
*/
|
||||
export class AppDropZone {
|
||||
private scene: Scene;
|
||||
private dropZones: Mesh[] = [];
|
||||
private dropZoneConfigs: Map<string, any[]> = new Map(); // 存储每个模型的放置区域配置
|
||||
private boundingBoxLines: Mesh[] = []; // 存储包围盒线框
|
||||
private placementWall: AppPlacementWall;
|
||||
|
||||
constructor(scene: Scene) {
|
||||
this.scene = scene;
|
||||
this.placementWall = new AppPlacementWall(scene);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据模型包围盒生成四周的放置区域
|
||||
* 生成放置区域
|
||||
* @param config 配置参数
|
||||
*/
|
||||
generateDropZones(config: DropZoneConfig): Mesh[] {
|
||||
const {
|
||||
modelName,
|
||||
divisions,
|
||||
color = '#21c7ff',
|
||||
alpha = 0.3,
|
||||
thickness = 2,
|
||||
offset = 5,
|
||||
scale = 1.0
|
||||
} = config;
|
||||
|
||||
// 查找目标模型(支持 modelId 或 mesh name)
|
||||
let targetMeshes: AbstractMesh[] | undefined;
|
||||
|
||||
// 先尝试通过 modelId 查找(从 AppModel 的 modelDic)
|
||||
const mainApp = (this.scene as any).mainApp;
|
||||
if (mainApp?.appModel) {
|
||||
targetMeshes = mainApp.appModel.getCachedMeshes(modelName);
|
||||
}
|
||||
|
||||
// 如果没找到,尝试通过 mesh name 查找
|
||||
if (!targetMeshes || targetMeshes.length === 0) {
|
||||
const mesh = this.scene.getMeshByName(modelName);
|
||||
if (mesh) {
|
||||
targetMeshes = [mesh];
|
||||
}
|
||||
}
|
||||
|
||||
if (!targetMeshes || targetMeshes.length === 0) {
|
||||
console.warn(`模型 ${modelName} 不存在`);
|
||||
return [];
|
||||
}
|
||||
|
||||
// 计算所有网格的总包围盒(使用世界坐标)
|
||||
let minX = Infinity, minY = Infinity, minZ = Infinity;
|
||||
let maxX = -Infinity, maxY = -Infinity, maxZ = -Infinity;
|
||||
|
||||
targetMeshes.forEach(mesh => {
|
||||
// 强制更新世界矩阵
|
||||
mesh.computeWorldMatrix(true);
|
||||
const boundingInfo = mesh.getBoundingInfo();
|
||||
|
||||
// 获取世界空间的包围盒
|
||||
const worldMin = boundingInfo.boundingBox.minimumWorld;
|
||||
const worldMax = boundingInfo.boundingBox.maximumWorld;
|
||||
|
||||
minX = Math.min(minX, worldMin.x);
|
||||
minY = Math.min(minY, worldMin.y);
|
||||
minZ = Math.min(minZ, worldMin.z);
|
||||
maxX = Math.max(maxX, worldMax.x);
|
||||
maxY = Math.max(maxY, worldMax.y);
|
||||
maxZ = Math.max(maxZ, worldMax.z);
|
||||
});
|
||||
|
||||
console.log('包围盒坐标:', { minX, minY, minZ, maxX, maxY, maxZ });
|
||||
console.log('包围盒尺寸:', {
|
||||
width: maxX - minX,
|
||||
height: maxY - minY,
|
||||
depth: maxZ - minZ
|
||||
});
|
||||
|
||||
const width = maxX - minX;
|
||||
const height = maxY - minY;
|
||||
const depth = maxZ - minZ;
|
||||
|
||||
// 应用缩放比例
|
||||
const centerX = (minX + maxX) / 2;
|
||||
const centerY = (minY + maxY) / 2;
|
||||
const centerZ = (minZ + maxZ) / 2;
|
||||
|
||||
const scaledWidth = width * scale;
|
||||
const scaledHeight = height * scale;
|
||||
const scaledDepth = depth * scale;
|
||||
|
||||
const scaledMinX = centerX - scaledWidth / 2;
|
||||
const scaledMaxX = centerX + scaledWidth / 2;
|
||||
const scaledMinY = centerY - scaledHeight / 2;
|
||||
const scaledMaxY = centerY + scaledHeight / 2;
|
||||
const scaledMinZ = centerZ - scaledDepth / 2;
|
||||
const scaledMaxZ = centerZ + scaledDepth / 2;
|
||||
|
||||
// 计算每块的尺寸
|
||||
const blockWidth = scaledWidth / divisions;
|
||||
const blockDepth = scaledDepth / divisions;
|
||||
|
||||
const zones: Mesh[] = [];
|
||||
const zoneConfigs: any[] = [];
|
||||
|
||||
// 创建材质
|
||||
const material = this.createDropZoneMaterial(color, alpha);
|
||||
|
||||
// 前面(Z轴负方向)
|
||||
for (let i = 0; i < divisions; i++) {
|
||||
const x = scaledMinX + blockWidth * i + blockWidth / 2;
|
||||
const z = scaledMinZ - offset;
|
||||
const position = new Vector3(x, scaledMinY, z);
|
||||
const zone = this.createDropZonePlane(
|
||||
`dropZone_${modelName}_front_${i}`,
|
||||
blockWidth,
|
||||
scaledHeight,
|
||||
position,
|
||||
0,
|
||||
material,
|
||||
thickness
|
||||
);
|
||||
zones.push(zone);
|
||||
zoneConfigs.push({
|
||||
position: position.clone(),
|
||||
width: blockWidth,
|
||||
height: scaledHeight,
|
||||
rotation: 0,
|
||||
side: 'front',
|
||||
index: i
|
||||
});
|
||||
}
|
||||
|
||||
// 后面(Z轴正方向)
|
||||
for (let i = 0; i < divisions; i++) {
|
||||
const x = scaledMinX + blockWidth * i + blockWidth / 2;
|
||||
const z = scaledMaxZ + offset;
|
||||
const position = new Vector3(x, scaledMinY, z);
|
||||
const zone = this.createDropZonePlane(
|
||||
`dropZone_${modelName}_back_${i}`,
|
||||
blockWidth,
|
||||
scaledHeight,
|
||||
position,
|
||||
0,
|
||||
material,
|
||||
thickness
|
||||
);
|
||||
zones.push(zone);
|
||||
zoneConfigs.push({
|
||||
position: position.clone(),
|
||||
width: blockWidth,
|
||||
height: scaledHeight,
|
||||
rotation: 0,
|
||||
side: 'back',
|
||||
index: i
|
||||
});
|
||||
}
|
||||
|
||||
// 左侧(X轴负方向)
|
||||
for (let i = 0; i < divisions; i++) {
|
||||
const x = scaledMinX - offset;
|
||||
const z = scaledMinZ + blockDepth * i + blockDepth / 2;
|
||||
const position = new Vector3(x, scaledMinY, z);
|
||||
const zone = this.createDropZonePlane(
|
||||
`dropZone_${modelName}_left_${i}`,
|
||||
blockDepth,
|
||||
scaledHeight,
|
||||
position,
|
||||
Math.PI / 2,
|
||||
material,
|
||||
thickness
|
||||
);
|
||||
zones.push(zone);
|
||||
zoneConfigs.push({
|
||||
position: position.clone(),
|
||||
width: blockDepth,
|
||||
height: scaledHeight,
|
||||
rotation: Math.PI / 2,
|
||||
side: 'left',
|
||||
index: i
|
||||
});
|
||||
}
|
||||
|
||||
// 右侧(X轴正方向)
|
||||
for (let i = 0; i < divisions; i++) {
|
||||
const x = scaledMaxX + offset;
|
||||
const z = scaledMinZ + blockDepth * i + blockDepth / 2;
|
||||
const position = new Vector3(x, scaledMinY, z);
|
||||
const zone = this.createDropZonePlane(
|
||||
`dropZone_${modelName}_right_${i}`,
|
||||
blockDepth,
|
||||
scaledHeight,
|
||||
position,
|
||||
Math.PI / 2,
|
||||
material,
|
||||
thickness
|
||||
);
|
||||
zones.push(zone);
|
||||
zoneConfigs.push({
|
||||
position: position.clone(),
|
||||
width: blockDepth,
|
||||
height: scaledHeight,
|
||||
rotation: Math.PI / 2,
|
||||
side: 'right',
|
||||
index: i
|
||||
});
|
||||
}
|
||||
|
||||
// 保存配置
|
||||
this.dropZoneConfigs.set(modelName, zoneConfigs);
|
||||
this.dropZones.push(...zones);
|
||||
|
||||
// 显示包围盒
|
||||
this.showBoundingBox(modelName, '#ff0000');
|
||||
|
||||
// 默认隐藏
|
||||
zones.forEach(zone => zone.setEnabled(false));
|
||||
|
||||
return zones;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建单个放置区域平面
|
||||
*/
|
||||
private createDropZonePlane(
|
||||
name: string,
|
||||
width: number,
|
||||
height: number,
|
||||
position: Vector3,
|
||||
rotationY: number,
|
||||
material: StandardMaterial,
|
||||
thickness: number
|
||||
): Mesh {
|
||||
// 创建主平面
|
||||
const plane = MeshBuilder.CreatePlane(name, {
|
||||
width: width,
|
||||
height: height
|
||||
}, this.scene);
|
||||
|
||||
plane.position = position;
|
||||
plane.rotation.y = rotationY;
|
||||
plane.material = material;
|
||||
plane.isPickable = true; // 可以被拾取,用于检测拖拽
|
||||
plane.metadata = { isDropZone: true }; // 标记为放置区域
|
||||
|
||||
// 创建边框线
|
||||
this.createBorder(plane, width, height, thickness);
|
||||
|
||||
return plane;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建边框线
|
||||
*/
|
||||
private createBorder(parent: Mesh, width: number, height: number, thickness: number): void {
|
||||
const halfWidth = width / 2;
|
||||
const halfHeight = height / 2;
|
||||
|
||||
const points = [
|
||||
new Vector3(-halfWidth, -halfHeight, -0.01),
|
||||
new Vector3(halfWidth, -halfHeight, -0.01),
|
||||
new Vector3(halfWidth, halfHeight, -0.01),
|
||||
new Vector3(-halfWidth, halfHeight, -0.01),
|
||||
new Vector3(-halfWidth, -halfHeight, -0.01)
|
||||
];
|
||||
|
||||
const border = MeshBuilder.CreateLines(`${parent.name}_border`, {
|
||||
points: points
|
||||
}, this.scene);
|
||||
|
||||
border.color = new Color3(1, 1, 1); // 白色边框
|
||||
border.parent = parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建放置区域材质
|
||||
*/
|
||||
private createDropZoneMaterial(hexColor: string, alpha: number): StandardMaterial {
|
||||
const material = new StandardMaterial('dropZoneMat_' + Date.now(), this.scene);
|
||||
const rgb = this.hexToRgb(hexColor);
|
||||
material.diffuseColor = new Color3(rgb.r, rgb.g, rgb.b);
|
||||
material.alpha = alpha;
|
||||
material.backFaceCulling = false; // 双面显示
|
||||
return material;
|
||||
}
|
||||
|
||||
/**
|
||||
* 十六进制颜色转 RGB
|
||||
*/
|
||||
private hexToRgb(hex: string): { r: number; g: number; b: number } {
|
||||
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
||||
return result ? {
|
||||
r: parseInt(result[1], 16) / 255,
|
||||
g: parseInt(result[2], 16) / 255,
|
||||
b: parseInt(result[3], 16) / 255
|
||||
} : { r: 0.13, g: 0.78, b: 1 };
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示所有放置区域
|
||||
*/
|
||||
showAllDropZones(): void {
|
||||
this.dropZones.forEach(zone => zone.setEnabled(true));
|
||||
// 显示包围盒
|
||||
this.boundingBoxLines.forEach(line => line.setEnabled(true));
|
||||
}
|
||||
|
||||
/**
|
||||
* 隐藏所有放置区域
|
||||
*/
|
||||
hideAllDropZones(): void {
|
||||
this.dropZones.forEach(zone => zone.setEnabled(false));
|
||||
// 隐藏包围盒
|
||||
this.boundingBoxLines.forEach(line => line.setEnabled(false));
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示指定模型的放置区域
|
||||
*/
|
||||
showDropZonesForModel(modelName: string): void {
|
||||
this.dropZones
|
||||
.filter(zone => zone.name.includes(`dropZone_${modelName}_`))
|
||||
.forEach(zone => zone.setEnabled(true));
|
||||
}
|
||||
|
||||
/**
|
||||
* 隐藏指定模型的放置区域
|
||||
*/
|
||||
hideDropZonesForModel(modelName: string): void {
|
||||
this.dropZones
|
||||
.filter(zone => zone.name.includes(`dropZone_${modelName}_`))
|
||||
.forEach(zone => zone.setEnabled(false));
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查某个位置是否在放置区域内
|
||||
* @param position 要检查的位置
|
||||
* @returns 如果在放置区域内,返回该区域的配置信息,否则返回 null
|
||||
*/
|
||||
checkInDropZone(position: Vector3): { zone: Mesh; config: any } | null {
|
||||
for (const zone of this.dropZones) {
|
||||
if (!zone.isEnabled()) continue;
|
||||
|
||||
// 简单的距离检测
|
||||
const distance = Vector3.Distance(
|
||||
new Vector3(position.x, 0, position.z),
|
||||
new Vector3(zone.position.x, 0, zone.position.z)
|
||||
);
|
||||
|
||||
// 获取区域的宽度(从 scaling 或原始尺寸计算)
|
||||
const zoneBounds = zone.getBoundingInfo();
|
||||
const zoneSize = zoneBounds.boundingBox.extendSize;
|
||||
const maxDistance = Math.max(zoneSize.x, zoneSize.z);
|
||||
|
||||
if (distance < maxDistance) {
|
||||
// 找到对应的配置
|
||||
const modelName = zone.name.split('_')[1];
|
||||
const configs = this.dropZoneConfigs.get(modelName);
|
||||
const configIndex = parseInt(zone.name.split('_').pop() || '0');
|
||||
const config = configs ? configs.find(c => c.index === configIndex) : null;
|
||||
|
||||
return { zone, config };
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 高亮某个放置区域(鼠标悬停效果)
|
||||
*/
|
||||
highlightDropZone(zone: Mesh): void {
|
||||
const material = zone.material as StandardMaterial;
|
||||
if (material) {
|
||||
material.alpha = 0.6; // 增加透明度
|
||||
material.emissiveColor = new Color3(0.2, 0.2, 0.2); // 添加发光效果
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消高亮
|
||||
*/
|
||||
unhighlightDropZone(zone: Mesh): void {
|
||||
const material = zone.material as StandardMaterial;
|
||||
if (material) {
|
||||
material.alpha = 0.3; // 恢复透明度
|
||||
material.emissiveColor = new Color3(0, 0, 0); // 移除发光效果
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除所有放置区域
|
||||
*/
|
||||
clearAllDropZones(): void {
|
||||
this.dropZones.forEach(zone => {
|
||||
zone.dispose();
|
||||
});
|
||||
this.dropZones = [];
|
||||
this.dropZoneConfigs.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除指定模型的放置区域
|
||||
*/
|
||||
clearDropZonesForModel(modelName: string): void {
|
||||
const zonesToRemove = this.dropZones.filter(zone =>
|
||||
zone.name.includes(`dropZone_${modelName}_`)
|
||||
);
|
||||
|
||||
zonesToRemove.forEach(zone => {
|
||||
zone.dispose();
|
||||
const index = this.dropZones.indexOf(zone);
|
||||
if (index > -1) {
|
||||
this.dropZones.splice(index, 1);
|
||||
}
|
||||
});
|
||||
|
||||
this.dropZoneConfigs.delete(modelName);
|
||||
generateDropZones(config: DropZoneConfig): PlacementZoneInfo[] {
|
||||
return this.placementWall.generatePlacementAreas(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有放置区域
|
||||
*/
|
||||
getAllDropZones(): Mesh[] {
|
||||
return this.dropZones;
|
||||
getPlacementZones(): PlacementZoneInfo[] {
|
||||
return this.placementWall.getPlacementZones();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定模型的放置区域配置
|
||||
* 根据墙面名称获取放置区域
|
||||
*/
|
||||
getDropZoneConfigsForModel(modelName: string): any[] {
|
||||
return this.dropZoneConfigs.get(modelName) || [];
|
||||
getZonesByWall(wallName: string): PlacementZoneInfo[] {
|
||||
return this.placementWall.getZonesByWall(wallName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示模型的包围盒
|
||||
* @param modelName 模型名称
|
||||
* @param color 包围盒颜色
|
||||
* 根据索引获取特定放置区域
|
||||
*/
|
||||
private showBoundingBox(modelName: string, color: string = '#ff0000'): void {
|
||||
// 查找目标模型
|
||||
let targetMeshes: AbstractMesh[] | undefined;
|
||||
|
||||
const mainApp = (this.scene as any).mainApp;
|
||||
if (mainApp?.appModel) {
|
||||
targetMeshes = mainApp.appModel.getCachedMeshes(modelName);
|
||||
}
|
||||
|
||||
if (!targetMeshes || targetMeshes.length === 0) {
|
||||
const mesh = this.scene.getMeshByName(modelName);
|
||||
if (mesh) {
|
||||
targetMeshes = [mesh];
|
||||
}
|
||||
}
|
||||
|
||||
if (!targetMeshes || targetMeshes.length === 0) {
|
||||
console.warn(`模型 ${modelName} 不存在`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 计算总包围盒(使用世界坐标)
|
||||
let minX = Infinity, minY = Infinity, minZ = Infinity;
|
||||
let maxX = -Infinity, maxY = -Infinity, maxZ = -Infinity;
|
||||
|
||||
targetMeshes.forEach(mesh => {
|
||||
// 强制更新世界矩阵
|
||||
mesh.computeWorldMatrix(true);
|
||||
const boundingInfo = mesh.getBoundingInfo();
|
||||
|
||||
// 获取世界空间的包围盒
|
||||
const worldMin = boundingInfo.boundingBox.minimumWorld;
|
||||
const worldMax = boundingInfo.boundingBox.maximumWorld;
|
||||
|
||||
minX = Math.min(minX, worldMin.x);
|
||||
minY = Math.min(minY, worldMin.y);
|
||||
minZ = Math.min(minZ, worldMin.z);
|
||||
maxX = Math.max(maxX, worldMax.x);
|
||||
maxY = Math.max(maxY, worldMax.y);
|
||||
maxZ = Math.max(maxZ, worldMax.z);
|
||||
});
|
||||
|
||||
// 创建包围盒的8个顶点
|
||||
const corners = [
|
||||
new Vector3(minX, minY, minZ),
|
||||
new Vector3(maxX, minY, minZ),
|
||||
new Vector3(maxX, minY, maxZ),
|
||||
new Vector3(minX, minY, maxZ),
|
||||
new Vector3(minX, maxY, minZ),
|
||||
new Vector3(maxX, maxY, minZ),
|
||||
new Vector3(maxX, maxY, maxZ),
|
||||
new Vector3(minX, maxY, maxZ)
|
||||
];
|
||||
|
||||
// 创建12条边
|
||||
const edges = [
|
||||
// 底面4条边
|
||||
[corners[0], corners[1]],
|
||||
[corners[1], corners[2]],
|
||||
[corners[2], corners[3]],
|
||||
[corners[3], corners[0]],
|
||||
// 顶面4条边
|
||||
[corners[4], corners[5]],
|
||||
[corners[5], corners[6]],
|
||||
[corners[6], corners[7]],
|
||||
[corners[7], corners[4]],
|
||||
// 4条竖边
|
||||
[corners[0], corners[4]],
|
||||
[corners[1], corners[5]],
|
||||
[corners[2], corners[6]],
|
||||
[corners[3], corners[7]]
|
||||
];
|
||||
|
||||
const rgb = this.hexToRgb(color);
|
||||
const lineColor = new Color3(rgb.r, rgb.g, rgb.b);
|
||||
|
||||
edges.forEach((edge, index) => {
|
||||
const line = MeshBuilder.CreateLines(`boundingBox_${modelName}_${index}`, {
|
||||
points: edge
|
||||
}, this.scene);
|
||||
line.color = lineColor;
|
||||
this.boundingBoxLines.push(line);
|
||||
});
|
||||
getZone(wallName: string, index: number): PlacementZoneInfo | undefined {
|
||||
return this.placementWall.getZone(wallName, index);
|
||||
}
|
||||
|
||||
/**
|
||||
* 隐藏所有包围盒
|
||||
* 显示所有放置区域
|
||||
*/
|
||||
private hideBoundingBox(): void {
|
||||
this.boundingBoxLines.forEach(line => line.dispose());
|
||||
this.boundingBoxLines = [];
|
||||
show(): void {
|
||||
this.placementWall.show();
|
||||
}
|
||||
|
||||
/**
|
||||
* 隐藏所有放置区域
|
||||
*/
|
||||
hide(): void {
|
||||
this.placementWall.hide();
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除所有放置区域
|
||||
*/
|
||||
clearAll(): void {
|
||||
this.placementWall.clearAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁
|
||||
*/
|
||||
dispose(): void {
|
||||
this.placementWall.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
331
src/babylonjs/AppPlacementWall.ts
Normal file
331
src/babylonjs/AppPlacementWall.ts
Normal file
@ -0,0 +1,331 @@
|
||||
import { Scene, Mesh, MeshBuilder, StandardMaterial, Color3, Vector3 } from '@babylonjs/core';
|
||||
|
||||
/**
|
||||
* 墙面配置 - 定义一个垂直面的起始和结束坐标
|
||||
*/
|
||||
export interface WallConfig {
|
||||
name: string; // 墙面名称(如 "front", "back", "left", "right")
|
||||
startPoint: Vector3; // 起始点坐标(左下角)
|
||||
endPoint: Vector3; // 结束点坐标(右下角)
|
||||
height: number; // 墙面高度
|
||||
divisions: number; // 分割数(将这个面分成几块)
|
||||
}
|
||||
|
||||
/**
|
||||
* 放置区域配置
|
||||
*/
|
||||
export interface PlacementAreaConfig {
|
||||
walls: WallConfig[]; // 墙面数组(可以定义1-N个面)
|
||||
color?: string; // 颜色(十六进制)
|
||||
alpha?: number; // 透明度
|
||||
thickness?: number; // 厚度
|
||||
showBorder?: boolean; // 是否显示边框
|
||||
borderColor?: string; // 边框颜色
|
||||
}
|
||||
|
||||
/**
|
||||
* 单个放置区域的信息
|
||||
*/
|
||||
export interface PlacementZoneInfo {
|
||||
mesh: Mesh; // 放置区域的网格
|
||||
wallName: string; // 所属墙面名称
|
||||
index: number; // 在该墙面上的索引
|
||||
center: Vector3; // 中心点坐标
|
||||
width: number; // 宽度
|
||||
height: number; // 高度
|
||||
normal: Vector3; // 法线方向
|
||||
}
|
||||
|
||||
export class AppPlacementWall {
|
||||
private scene: Scene;
|
||||
private placementZones: PlacementZoneInfo[] = [];
|
||||
private borderLines: Mesh[] = [];
|
||||
|
||||
constructor(scene: Scene) {
|
||||
this.scene = scene;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据墙面配置生成放置区域
|
||||
*/
|
||||
generatePlacementAreas(config: PlacementAreaConfig): PlacementZoneInfo[] {
|
||||
const {
|
||||
walls,
|
||||
color = '#21c7ff',
|
||||
alpha = 0.3,
|
||||
thickness = 2,
|
||||
showBorder = true,
|
||||
borderColor = '#ffffff'
|
||||
} = config;
|
||||
|
||||
// 清除之前的放置区域
|
||||
this.clearAll();
|
||||
|
||||
const material = this.createMaterial(color, alpha);
|
||||
|
||||
walls.forEach(wall => {
|
||||
const zones = this.generateWallZones(wall, material, thickness);
|
||||
this.placementZones.push(...zones);
|
||||
|
||||
if (showBorder) {
|
||||
this.createWallBorder(wall, borderColor);
|
||||
}
|
||||
});
|
||||
|
||||
return this.placementZones;
|
||||
}
|
||||
|
||||
/**
|
||||
* 为单个墙面生成放置区域
|
||||
*/
|
||||
private generateWallZones(
|
||||
wall: WallConfig,
|
||||
material: StandardMaterial,
|
||||
thickness: number
|
||||
): PlacementZoneInfo[] {
|
||||
const { name, startPoint, endPoint, height, divisions } = wall;
|
||||
|
||||
// 计算墙面的方向向量和长度
|
||||
const direction = endPoint.subtract(startPoint);
|
||||
const wallLength = direction.length();
|
||||
const normalizedDir = direction.normalize();
|
||||
|
||||
// 计算每块的宽度
|
||||
const blockWidth = wallLength / divisions;
|
||||
|
||||
// 计算墙面的法线方向(垂直于墙面,指向外侧)
|
||||
const normal = new Vector3(-normalizedDir.z, 0, normalizedDir.x);
|
||||
|
||||
const zones: PlacementZoneInfo[] = [];
|
||||
|
||||
for (let i = 0; i < divisions; i++) {
|
||||
// 计算当前块的中心位置
|
||||
const offset = blockWidth * (i + 0.5);
|
||||
const centerX = startPoint.x + normalizedDir.x * offset;
|
||||
const centerY = startPoint.y + height / 2;
|
||||
const centerZ = startPoint.z + normalizedDir.z * offset;
|
||||
const center = new Vector3(centerX, centerY, centerZ);
|
||||
|
||||
// 创建放置区域平面
|
||||
const plane = MeshBuilder.CreatePlane(
|
||||
`placement_${name}_${i}`,
|
||||
{ width: blockWidth, height: height },
|
||||
this.scene
|
||||
);
|
||||
|
||||
plane.position = center;
|
||||
plane.material = material;
|
||||
|
||||
// 让平面面向法线方向(垂直站立并朝外)
|
||||
// 使用 lookAt 让平面面向外侧
|
||||
const targetPoint = center.add(normal);
|
||||
plane.lookAt(targetPoint);
|
||||
|
||||
// 设置厚度(通过缩放Z轴)
|
||||
plane.scaling.z = thickness;
|
||||
|
||||
// 启用拾取
|
||||
plane.isPickable = true;
|
||||
|
||||
// 为每个块创建边框
|
||||
this.createBlockBorder(name, i, center, blockWidth, height, normalizedDir, normal);
|
||||
|
||||
zones.push({
|
||||
mesh: plane,
|
||||
wallName: name,
|
||||
index: i,
|
||||
center: center.clone(),
|
||||
width: blockWidth,
|
||||
height: height,
|
||||
normal: normal.clone()
|
||||
});
|
||||
}
|
||||
|
||||
return zones;
|
||||
}
|
||||
|
||||
/**
|
||||
* 为单个块创建边框
|
||||
*/
|
||||
private createBlockBorder(
|
||||
wallName: string,
|
||||
index: number,
|
||||
center: Vector3,
|
||||
width: number,
|
||||
height: number,
|
||||
direction: Vector3,
|
||||
normal: Vector3
|
||||
): void {
|
||||
const halfWidth = width / 2;
|
||||
const halfHeight = height / 2;
|
||||
|
||||
// 计算四个角点
|
||||
const bottomLeft = new Vector3(
|
||||
center.x - direction.x * halfWidth,
|
||||
center.y - halfHeight,
|
||||
center.z - direction.z * halfWidth
|
||||
);
|
||||
const bottomRight = new Vector3(
|
||||
center.x + direction.x * halfWidth,
|
||||
center.y - halfHeight,
|
||||
center.z + direction.z * halfWidth
|
||||
);
|
||||
const topLeft = new Vector3(
|
||||
center.x - direction.x * halfWidth,
|
||||
center.y + halfHeight,
|
||||
center.z - direction.z * halfWidth
|
||||
);
|
||||
const topRight = new Vector3(
|
||||
center.x + direction.x * halfWidth,
|
||||
center.y + halfHeight,
|
||||
center.z + direction.z * halfWidth
|
||||
);
|
||||
|
||||
// 创建四条边
|
||||
const edges = [
|
||||
[bottomLeft, bottomRight], // 底边
|
||||
[bottomRight, topRight], // 右边
|
||||
[topRight, topLeft], // 顶边
|
||||
[topLeft, bottomLeft] // 左边
|
||||
];
|
||||
|
||||
edges.forEach((edge, edgeIndex) => {
|
||||
const line = MeshBuilder.CreateLines(
|
||||
`block_border_${wallName}_${index}_${edgeIndex}`,
|
||||
{ points: edge },
|
||||
this.scene
|
||||
);
|
||||
line.color = new Color3(1, 1, 1); // 白色
|
||||
this.borderLines.push(line);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建墙面边框
|
||||
*/
|
||||
private createWallBorder(wall: WallConfig, color: string): void {
|
||||
const { name, startPoint, endPoint, height } = wall;
|
||||
|
||||
// 计算四个角点
|
||||
const bottomLeft = startPoint.clone();
|
||||
const bottomRight = endPoint.clone();
|
||||
const topLeft = new Vector3(startPoint.x, startPoint.y + height, startPoint.z);
|
||||
const topRight = new Vector3(endPoint.x, endPoint.y + height, endPoint.z);
|
||||
|
||||
// 创建四条边
|
||||
const edges = [
|
||||
[bottomLeft, bottomRight], // 底边
|
||||
[bottomRight, topRight], // 右边
|
||||
[topRight, topLeft], // 顶边
|
||||
[topLeft, bottomLeft] // 左边
|
||||
];
|
||||
|
||||
const rgb = this.hexToRgb(color);
|
||||
const lineColor = new Color3(rgb.r, rgb.g, rgb.b);
|
||||
|
||||
edges.forEach((edge, index) => {
|
||||
const line = MeshBuilder.CreateLines(
|
||||
`border_${name}_${index}`,
|
||||
{ points: edge },
|
||||
this.scene
|
||||
);
|
||||
line.color = lineColor;
|
||||
this.borderLines.push(line);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建材质
|
||||
*/
|
||||
private createMaterial(color: string, alpha: number): StandardMaterial {
|
||||
const material = new StandardMaterial('placementMaterial', this.scene);
|
||||
const rgb = this.hexToRgb(color);
|
||||
material.diffuseColor = new Color3(rgb.r, rgb.g, rgb.b);
|
||||
material.alpha = alpha;
|
||||
material.backFaceCulling = false;
|
||||
return material;
|
||||
}
|
||||
|
||||
/**
|
||||
* 十六进制颜色转RGB
|
||||
*/
|
||||
private hexToRgb(hex: string): { r: number; g: number; b: number } {
|
||||
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
||||
return result
|
||||
? {
|
||||
r: parseInt(result[1], 16) / 255,
|
||||
g: parseInt(result[2], 16) / 255,
|
||||
b: parseInt(result[3], 16) / 255
|
||||
}
|
||||
: { r: 0, g: 0, b: 0 };
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有放置区域
|
||||
*/
|
||||
getPlacementZones(): PlacementZoneInfo[] {
|
||||
return this.placementZones;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据墙面名称获取放置区域
|
||||
*/
|
||||
getZonesByWall(wallName: string): PlacementZoneInfo[] {
|
||||
return this.placementZones.filter(zone => zone.wallName === wallName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据索引获取特定放置区域
|
||||
*/
|
||||
getZone(wallName: string, index: number): PlacementZoneInfo | undefined {
|
||||
return this.placementZones.find(
|
||||
zone => zone.wallName === wallName && zone.index === index
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示所有放置区域
|
||||
*/
|
||||
show(): void {
|
||||
this.placementZones.forEach(zone => {
|
||||
zone.mesh.isVisible = true;
|
||||
});
|
||||
this.borderLines.forEach(line => {
|
||||
line.isVisible = true;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 隐藏所有放置区域
|
||||
*/
|
||||
hide(): void {
|
||||
this.placementZones.forEach(zone => {
|
||||
zone.mesh.isVisible = false;
|
||||
});
|
||||
this.borderLines.forEach(line => {
|
||||
line.isVisible = false;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除所有放置区域
|
||||
*/
|
||||
clearAll(): void {
|
||||
this.placementZones.forEach(zone => {
|
||||
zone.mesh.dispose();
|
||||
});
|
||||
this.placementZones = [];
|
||||
|
||||
this.borderLines.forEach(line => {
|
||||
line.dispose();
|
||||
});
|
||||
this.borderLines = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁
|
||||
*/
|
||||
dispose(): void {
|
||||
this.clearAll();
|
||||
}
|
||||
}
|
||||
@ -1,3 +1,4 @@
|
||||
import { Vector3 } from '@babylonjs/core';
|
||||
import { MainApp } from '../babylonjs/MainApp';
|
||||
import type { HotspotInput } from '../types/hotspot';
|
||||
|
||||
@ -218,71 +219,145 @@ export class KernelAdapter {
|
||||
/** 放置区域管理 */
|
||||
dropZone = {
|
||||
/**
|
||||
* 根据模型包围盒生成四周的放置区域
|
||||
* 生成放置区域
|
||||
* @param options 配置选项
|
||||
* @example
|
||||
* kernel.dropZone.generate({
|
||||
* modelName: "框架",
|
||||
* divisions: 4,
|
||||
* walls: [
|
||||
* {
|
||||
* name: 'front',
|
||||
* startPoint: new Vector3(-50, 0, -50),
|
||||
* endPoint: new Vector3(50, 0, -50),
|
||||
* height: 30,
|
||||
* divisions: 5
|
||||
* }
|
||||
* ],
|
||||
* color: "#21c7ff",
|
||||
* alpha: 0.3,
|
||||
* scale: 0.8 // 缩小到80%,用于内部放置区域
|
||||
* alpha: 0.3
|
||||
* });
|
||||
*/
|
||||
generate: (options: {
|
||||
modelName: string;
|
||||
walls: Array<{
|
||||
name: string;
|
||||
startPoint: [number, number, number];
|
||||
endPoint: [number, number, number];
|
||||
height: number;
|
||||
divisions: number;
|
||||
}>;
|
||||
color?: string;
|
||||
alpha?: number;
|
||||
thickness?: number;
|
||||
offset?: number;
|
||||
scale?: number;
|
||||
showBorder?: boolean;
|
||||
borderColor?: string;
|
||||
}): any[] => {
|
||||
return this.mainApp.appDropZone.generateDropZones(options);
|
||||
|
||||
// 转换数组坐标为 Vector3
|
||||
const config = {
|
||||
...options,
|
||||
walls: options.walls.map(wall => ({
|
||||
...wall,
|
||||
startPoint: new Vector3(wall.startPoint[0], wall.startPoint[1], wall.startPoint[2]),
|
||||
endPoint: new Vector3(wall.endPoint[0], wall.endPoint[1], wall.endPoint[2])
|
||||
}))
|
||||
};
|
||||
|
||||
return this.mainApp.appDropZone.generateDropZones(config);
|
||||
},
|
||||
/**
|
||||
* 显示所有放置区域
|
||||
*/
|
||||
showAll: (): void => {
|
||||
this.mainApp.appDropZone.showAllDropZones();
|
||||
this.mainApp.appDropZone.show();
|
||||
},
|
||||
/**
|
||||
* 隐藏所有放置区域
|
||||
*/
|
||||
hideAll: (): void => {
|
||||
this.mainApp.appDropZone.hideAllDropZones();
|
||||
this.mainApp.appDropZone.hide();
|
||||
},
|
||||
/**
|
||||
* 显示指定模型的放置区域
|
||||
* 根据墙面名称显示放置区域
|
||||
*/
|
||||
show: (modelName: string): void => {
|
||||
this.mainApp.appDropZone.showDropZonesForModel(modelName);
|
||||
show: (wallName: string): void => {
|
||||
const zones = this.mainApp.appDropZone.getZonesByWall(wallName);
|
||||
zones.forEach(zone => {
|
||||
zone.mesh.isVisible = true;
|
||||
});
|
||||
},
|
||||
/**
|
||||
* 隐藏指定模型的放置区域
|
||||
* 根据墙面名称隐藏放置区域
|
||||
*/
|
||||
hide: (modelName: string): void => {
|
||||
this.mainApp.appDropZone.hideDropZonesForModel(modelName);
|
||||
hide: (wallName: string): void => {
|
||||
const zones = this.mainApp.appDropZone.getZonesByWall(wallName);
|
||||
zones.forEach(zone => {
|
||||
zone.mesh.isVisible = false;
|
||||
});
|
||||
},
|
||||
/**
|
||||
* 清除所有放置区域
|
||||
*/
|
||||
clearAll: (): void => {
|
||||
this.mainApp.appDropZone.clearAllDropZones();
|
||||
this.mainApp.appDropZone.clearAll();
|
||||
},
|
||||
/**
|
||||
* 清除指定模型的放置区域
|
||||
* 获取所有放置区域
|
||||
*/
|
||||
clear: (modelName: string): void => {
|
||||
this.mainApp.appDropZone.clearDropZonesForModel(modelName);
|
||||
getAll: (): any[] => {
|
||||
return this.mainApp.appDropZone.getPlacementZones();
|
||||
},
|
||||
/**
|
||||
* 根据墙面名称获取放置区域
|
||||
*/
|
||||
getByWall: (wallName: string): any[] => {
|
||||
return this.mainApp.appDropZone.getZonesByWall(wallName);
|
||||
},
|
||||
/**
|
||||
* 获取特定的放置区域
|
||||
*/
|
||||
getZone: (wallName: string, index: number): any => {
|
||||
return this.mainApp.appDropZone.getZone(wallName, index);
|
||||
},
|
||||
/**
|
||||
* 检查某个位置是否在放置区域内
|
||||
*/
|
||||
checkPosition: (position: [number, number, number]): any => {
|
||||
const { Vector3 } = require('@babylonjs/core');
|
||||
|
||||
const pos = new Vector3(position[0], position[1], position[2]);
|
||||
return this.mainApp.appDropZone.checkInDropZone(pos);
|
||||
|
||||
const zones = this.mainApp.appDropZone.getPlacementZones();
|
||||
|
||||
for (const zone of zones) {
|
||||
const halfWidth = zone.width / 2;
|
||||
const halfHeight = zone.height / 2;
|
||||
|
||||
const localPos = pos.subtract(zone.center);
|
||||
const distance = Math.abs(
|
||||
localPos.x * zone.normal.x +
|
||||
localPos.z * zone.normal.z
|
||||
);
|
||||
|
||||
// 检查是否在平面附近
|
||||
if (distance < 5) {
|
||||
const alongWidth = Math.abs(localPos.x * (1 - Math.abs(zone.normal.x)) +
|
||||
localPos.z * (1 - Math.abs(zone.normal.z)));
|
||||
const alongHeight = Math.abs(localPos.y);
|
||||
|
||||
if (alongWidth <= halfWidth && alongHeight <= halfHeight) {
|
||||
return {
|
||||
inZone: true,
|
||||
zone: zone,
|
||||
wallName: zone.wallName,
|
||||
index: zone.index,
|
||||
center: zone.center
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
inZone: false,
|
||||
zone: null
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
23
test-rotation.js
Normal file
23
test-rotation.js
Normal file
@ -0,0 +1,23 @@
|
||||
// 测试不同的旋转方案
|
||||
|
||||
// 方案1:只旋转Y轴(当前方案)
|
||||
plane.rotation.y = angle;
|
||||
|
||||
// 方案2:先X轴90度,再Y轴
|
||||
plane.rotation.x = Math.PI / 2;
|
||||
plane.rotation.y = angle;
|
||||
|
||||
// 方案3:使用lookAt
|
||||
plane.lookAt(center.add(normal));
|
||||
|
||||
// 方案4:使用四元数旋转
|
||||
const forward = new Vector3(0, 0, -1); // 平面默认法线
|
||||
const targetNormal = normal.normalize();
|
||||
const quaternion = Quaternion.FromUnitVectorsToRef(forward, targetNormal, new Quaternion());
|
||||
plane.rotationQuaternion = quaternion;
|
||||
|
||||
// 方案5:手动设置旋转(根据墙面方向)
|
||||
// 前墙 (Z负方向): rotation.y = 0
|
||||
// 后墙 (Z正方向): rotation.y = Math.PI
|
||||
// 左墙 (X负方向): rotation.y = Math.PI / 2
|
||||
// 右墙 (X正方向): rotation.y = -Math.PI / 2
|
||||
BIN
wechat_2026-05-13_110515_365.png
Normal file
BIN
wechat_2026-05-13_110515_365.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 702 KiB |
Reference in New Issue
Block a user