This commit is contained in:
2026-05-13 11:28:49 +08:00
parent 223fa5dd4e
commit 21255a701d
12 changed files with 1765 additions and 537 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 676 KiB

View 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形等不规则形状
## 优势
- ✅ 精确控制每个墙面的位置
- ✅ 每个墙面可以独立配置
- ✅ 支持任意数量的墙面
- ✅ 可以创建不规则形状
- ✅ 不依赖模型,更灵活

View 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
View 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();
}

View 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;
}

View File

@ -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"
});
// 显示放置区域

View File

@ -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();
}
}

View 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();
}
}

View File

@ -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;
divisions: number;
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
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 702 KiB

4
设计
View File

@ -1,3 +1,5 @@
1.右侧添加选装选配的ui分类折叠棚子尺寸棚子/类型/百叶/配色 ,每个折叠下面都有四个属性 ,百叶是多选,其他的都是单选
2.页面布局也改下分画布和UI两部分
3.UI的事件预留好
3.UI的事件预留好
一共四个面,每个面都是一个对象 [wall1:{ },wall2:{ },wall3:{ },wall4:{ }] 这种wall1:{ } 是一个对象里面包含wall1的所有属性从哪到哪的长度