React Konva绘制2D房间:实现带厚度的外墙生成需求
React Konva 2D房间外墙生成解决方案
针对你需要生成厚度为20的外墙需求,以下是基于凸多边形外偏移的通用实现方案,完全匹配你给出的正方形示例,同时支持任意凸多边形房间的外墙生成:
核心思路
- 将原始内部点集转换为顶点数组(去除重复的闭合起点)
- 对每条边计算向外的单位法向量
- 基于相邻边的法向量平均方向,偏移每个顶点得到外墙顶点
- 连接内外对应顶点,形成契合的墙体
实现代码
工具函数
// 转换原始点数组为顶点坐标对(去除最后一个重复的闭合点) const getVertices = (points) => { const vertices = []; for (let i = 0; i < points.length - 2; i += 2) { vertices.push([points[i], points[i + 1]]); } return vertices; }; // 获取边的单位法向量(isOutward控制向外方向,适配顺时针顶点排列) const getUnitNormal = (edge, isOutward) => { // 顺时针多边形的向外法向量为 [edge.y, -edge.x] const normal = isOutward ? [edge[1], -edge[0]] : [-edge[1], edge[0]]; const length = Math.hypot(normal[0], normal[1]); return [normal[0] / length, normal[1] / length]; }; // 生成外偏移后的多边形点集 const offsetPolygon = (points, offset) => { const vertices = getVertices(points); const offsetVertices = []; const vertexCount = vertices.length; for (let i = 0; i < vertexCount; i++) { const current = vertices[i]; const next = vertices[(i + 1) % vertexCount]; const prev = vertices[(i - 1 + vertexCount) % vertexCount]; // 计算当前边和前一条边的向量 const edge1 = [next[0] - current[0], next[1] - current[1]]; const edge2 = [current[0] - prev[0], current[1] - prev[1]]; // 获取两条边的向外单位法向量 const normal1 = getUnitNormal(edge1, true); const normal2 = getUnitNormal(edge2, true); // 计算顶点偏移的平均方向并归一化 const avgNormal = [(normal1[0] + normal2[0]) / 2, (normal1[1] + normal2[1]) / 2]; const avgLength = Math.hypot(avgNormal[0], avgNormal[1]); const unitAvgNormal = [avgNormal[0] / avgLength, avgNormal[1] / avgLength]; // 计算偏移后的顶点坐标 offsetVertices.push([ current[0] + unitAvgNormal[0] * offset, current[1] + unitAvgNormal[1] * offset ]); } // 转换回原始点集格式并闭合 const offsetPoints = []; offsetVertices.forEach(v => offsetPoints.push(v[0], v[1])); offsetPoints.push(offsetVertices[0][0], offsetVertices[0][1]); return offsetPoints; };
React Konva组件集成
import { useState } from 'react'; import { Stage, Layer, Line } from 'react-konva'; const RoomDesigner = () => { // 内部房间点集(用户绘制完成后的闭合点) const [innerPoints, setInnerPoints] = useState([ 100, 100, 500, 100, 500, 500, 100, 500, 100, 100, ]); // 生成外墙点集(偏移量20) const outerPoints = offsetPolygon(innerPoints, 20); // 生成连接内外墙的墙体线段点集 const wallSegments = []; const innerVertices = getVertices(innerPoints); const outerVertices = getVertices(outerPoints); innerVertices.forEach((inner, idx) => { const outer = outerVertices[idx]; wallSegments.push(inner[0], inner[1], outer[0], outer[1], null); }); return ( <Stage width={800} height={600}> <Layer> {/* 内部空间轮廓 */} <Line points={innerPoints} stroke="#3498db" strokeWidth={2} closed /> {/* 外墙轮廓 */} <Line points={outerPoints} stroke="#2c3e50" strokeWidth={2} closed /> {/* 墙体实体(厚度20) */} <Line points={wallSegments} stroke="#7f8c8d" strokeWidth={20} lineCap="round" /> </Layer> </Stage> ); }; export default RoomDesigner;
关键注意事项
- 顶点排列顺序:代码默认内部点集为顺时针排列,若用户绘制是逆时针,需修改
getUnitNormal中的法向量为[-edge[1], edge[0]] - 适用范围:该方案仅适用于凸多边形房间(绝大多数实际场景),若需支持凹多边形,需额外引入多边形偏移的专用算法库
- 厚度匹配:墙体的
strokeWidth需与偏移量保持一致(示例中为20),确保视觉上的厚度符合需求
内容的提问来源于stack exchange,提问作者Ahmad Alshurafa
相关产品推荐
相关产品推荐

