import { Scene, Mesh, MeshBuilder, StandardMaterial, Color3, Vector3, ActionManager, ExecuteCodeAction } from '@babylonjs/core'; /** * 墙面配置 - 定义一个垂直面的起始和结束坐标 */ export interface WallConfig { name: string; // 墙面名称(如 "front", "back", "left", "right") startPoint: Vector3; // 起始点坐标(左下角) endPoint: Vector3; // 结束点坐标(右下角) height: number; // 墙面高度 divisions: number; // 分割数(将这个面分成几块) offset?: number; // 偏移量(正数向外,负数向内,默认0) } /** * 放置区域配置 */ 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[] = []; private onZoneClickCallback?: (zoneInfo: PlacementZoneInfo) => void; 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, offset = 0 } = 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 offsetVector = normal.scale(offset); const zones: PlacementZoneInfo[] = []; for (let i = 0; i < divisions; i++) { // 计算当前块的中心位置 const offset_i = blockWidth * (i + 0.5); const centerX = startPoint.x + normalizedDir.x * offset_i; const centerY = startPoint.y + height / 2; const centerZ = startPoint.z + normalizedDir.z * offset_i; const center = new Vector3(centerX, centerY, centerZ).add(offsetVector); // 创建放置区域平面 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; // 添加点击事件 plane.actionManager = new ActionManager(this.scene); plane.actionManager.registerAction( new ExecuteCodeAction( ActionManager.OnPickTrigger, () => { const zoneInfo: PlacementZoneInfo = { mesh: plane, wallName: name, index: i, center: center.clone(), width: blockWidth, height: height, normal: normal.clone() }; if (this.onZoneClickCallback) { this.onZoneClickCallback(zoneInfo); } } ) ); // 为每个块创建边框 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); // 白色 line.isPickable = false; // 禁用边框的点击 this.borderLines.push(line); }); } /** * 创建墙面边框 */ private createWallBorder(wall: WallConfig, color: string): void { const { name, startPoint, endPoint, height, offset = 0 } = wall; // 计算法线方向 const direction = endPoint.subtract(startPoint); const normalizedDir = direction.normalize(); const normal = new Vector3(-normalizedDir.z, 0, normalizedDir.x); // 应用偏移量 const offsetVector = normal.scale(offset); // 计算四个角点(应用偏移) const bottomLeft = startPoint.clone().add(offsetVector); const bottomRight = endPoint.clone().add(offsetVector); const topLeft = new Vector3(startPoint.x, startPoint.y + height, startPoint.z).add(offsetVector); const topRight = new Vector3(endPoint.x, endPoint.y + height, endPoint.z).add(offsetVector); // 创建四条边 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; line.isPickable = false; // 禁用边框的点击 this.borderLines.push(line); }); } /** * 创建材质 */ private createMaterial(color: string, alpha: number): StandardMaterial { const material = new StandardMaterial('placementMaterial', this.scene); const rgb = this.hexToRgb(color); material.emissiveColor = new Color3(rgb.r, rgb.g, rgb.b); material.disableLighting = true; 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 ); } /** * 设置点击回调 */ setOnZoneClick(callback: (zoneInfo: PlacementZoneInfo) => void): void { this.onZoneClickCallback = callback; } /** * 显示所有放置区域 */ 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(); } }