diff --git a/ScreenShot_2026-05-13_110749_413.png b/ScreenShot_2026-05-13_110749_413.png
new file mode 100644
index 0000000..ee96664
Binary files /dev/null and b/ScreenShot_2026-05-13_110749_413.png differ
diff --git a/docs/adapter-dropzone-api.md b/docs/adapter-dropzone-api.md
new file mode 100644
index 0000000..0589ef3
--- /dev/null
+++ b/docs/adapter-dropzone-api.md
@@ -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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+## 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形等不规则形状
+
+## 优势
+
+- ✅ 精确控制每个墙面的位置
+- ✅ 每个墙面可以独立配置
+- ✅ 支持任意数量的墙面
+- ✅ 可以创建不规则形状
+- ✅ 不依赖模型,更灵活
diff --git a/docs/placement-area-migration.md b/docs/placement-area-migration.md
new file mode 100644
index 0000000..fc45ff7
--- /dev/null
+++ b/docs/placement-area-migration.md
@@ -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
+
+虽然迁移需要一些工作,但新系统的灵活性和可控性值得这个投入!
diff --git a/examples/drop-zone-usage.ts b/examples/drop-zone-usage.ts
new file mode 100644
index 0000000..e952b6f
--- /dev/null
+++ b/examples/drop-zone-usage.ts
@@ -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();
+}
diff --git a/examples/placement-wall-example.ts b/examples/placement-wall-example.ts
new file mode 100644
index 0000000..105a6e7
--- /dev/null
+++ b/examples/placement-wall-example.ts
@@ -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;
+}
diff --git a/index.html b/index.html
index c611898..5ec68e8 100644
--- a/index.html
+++ b/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"
});
// 显示放置区域
diff --git a/src/babylonjs/AppDropZone.ts b/src/babylonjs/AppDropZone.ts
index b6e54e3..b4bedf0 100644
--- a/src/babylonjs/AppDropZone.ts
+++ b/src/babylonjs/AppDropZone.ts
@@ -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 = 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();
}
}
diff --git a/src/babylonjs/AppPlacementWall.ts b/src/babylonjs/AppPlacementWall.ts
new file mode 100644
index 0000000..e8ebe3a
--- /dev/null
+++ b/src/babylonjs/AppPlacementWall.ts
@@ -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();
+ }
+}
diff --git a/src/kernel/Adapter.ts b/src/kernel/Adapter.ts
index 3a5ba9b..5a9dbd1 100644
--- a/src/kernel/Adapter.ts
+++ b/src/kernel/Adapter.ts
@@ -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
+ };
}
};
diff --git a/test-rotation.js b/test-rotation.js
new file mode 100644
index 0000000..c7007a7
--- /dev/null
+++ b/test-rotation.js
@@ -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
diff --git a/wechat_2026-05-13_110515_365.png b/wechat_2026-05-13_110515_365.png
new file mode 100644
index 0000000..d32e3ce
Binary files /dev/null and b/wechat_2026-05-13_110515_365.png differ
diff --git a/设计 b/设计
index f8fb782..25fc8e0 100644
--- a/设计
+++ b/设计
@@ -1,3 +1,5 @@
1.右侧添加选装选配的ui,分类折叠,棚子尺寸棚子/类型/百叶/配色 ,每个折叠下面都有四个属性 ,百叶是多选,其他的都是单选
2.页面布局也改下,分画布和UI两部分
-3.UI的事件预留好
\ No newline at end of file
+3.UI的事件预留好
+
+一共四个面,每个面都是一个对象 [wall1:{ },wall2:{ },wall3:{ },wall4:{ }] 这种,wall1:{ } 是一个对象,里面包含wall1的所有属性,从哪到哪的长度,
\ No newline at end of file