npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

rm-wall-snap-3d

v1.1.47

Published

3D物体墙体吸附与位置修正引擎 - 支持家具、电源等自动适配墙体

Readme

rm-wall-snap-3d

3D物体墙体吸附与位置修正引擎 - 支持家具、电源等自动适配墙体

安装

npm install rm-wall-snap-3d

使用

基础用法

import { snapToWalls } from 'rm-wall-snap-3d';

// 只需要传入 items 和 walls
const response = snapToWalls({
  items: [
    {
      obj_id: 'socket_001',
      category: 'socket',
      box: {
        isBox3: true,
        min: { x: 1.0, y: 2.0, z: 1.4 },
        max: { x: 1.085, y: 2.085, z: 1.485 }
      },
      position: { x: 1.0425, y: 2.0425, z: 1.4425 },
      size: { x: 0.085, y: 0.085, z: 0.085 },
      contour: [...],  // 轮廓点数组
      direction: { x: 0, y: 1, z: 0 }
    }
  ],
  walls: [
    {
      center: { x: 0, y: 2.1, z: 1.5 },
      normal: { x: 0, y: -1, z: 0 },
      innerSurface: { x: 0, y: 2.0, z: 1.5 },
      outerSurface: { x: 0, y: 2.2, z: 1.5 }
    }
  ]
});

// 处理结果
if (response.success) {
  console.log(response.message);
  
  response.data.results.forEach(result => {
    if (result.snapped) {
      console.log(`✅ ${result.obj_id} 吸附成功`);
      console.log('新位置:', result.position);     // 吸附后的中心位置
      console.log('新轮廓:', result.contour);      // 吸附后的轮廓点
      console.log('新朝向:', result.direction);    // 墙面内侧方向
    }
  });
}

返回数据格式

{
  success: true,
  message: "吸附完成: 3/3 个物品成功吸附",
  data: {
    results: [
      {
        obj_id: "socket_001",           // 物体的唯一标识(保持不变)
        category: "socket",             // 物体类型(保持不变)
        snapped: true,                  // 是否成功吸附
        wallIndex: 0,                   // 吸附到的墙面索引
        distance: 0.015,                // 与墙面的距离(米)
        
        // 以下字段保持原始值不变
        box: {
          isBox3: true,
          min: { x: 1.0, y: 2.0, z: 1.4 },
          max: { x: 1.085, y: 2.085, z: 1.485 }
        },
        size: { x: 0.085, y: 0.085, z: 0.085 },
        
        // 以下字段根据吸附结果更新
        position: { x: 1.0425, y: 2.015, z: 1.4425 },  // 吸附后的新位置
        contour: [...],  // 吸附后的新轮廓点数组
        direction: { x: 0, y: -1, z: 0 }  // 墙面内侧方向
      }
    ]
  }
}

数据格式

Item(物品)

interface IItemInfo {
  obj_id: string;       // 唯一标识
  category: string;     // 类型:socket, switch, cabinet, air conditioner
  box: {
    isBox3: boolean;
    min: { x: number, y: number, z: number };  // 最小点
    max: { x: number, y: number, z: number };  // 最大点
  };
  position: { x: number, y: number, z: number };  // 中心位置
  size: { x: number, y: number, z: number };      // 尺寸
  contour: { x: number, y: number, z: number }[]; // 轮廓点数组
  direction: { x: number, y: number, z: number }; // 朝向
}

Wall(墙体)

interface IWallInfo {
  center: { x: number, y: number, z: number };       // 墙面中心点
  normal: { x: number, y: number, z: number };       // 墙面法向量(朝向)
  innerSurface: { x: number, y: number, z: number }; // 内表面位置
  outerSurface: { x: number, y: number, z: number }; // 外表面位置
}

Result(结果)

interface ISnapResult {
  obj_id: string;       // 物体唯一标识(不变)
  category: string;     // 物体类型(不变)
  snapped: boolean;     // 是否成功吸附
  wallIndex: number;    // 吸附到的墙面索引
  distance: number;     // 与墙面的距离
  
  // 保持不变
  box: { isBox3: boolean; min: IVector3; max: IVector3 };
  size: { x: number; y: number; z: number };
  
  // 根据吸附更新
  position: { x: number; y: number; z: number };   // 新位置
  contour: { x: number; y: number; z: number }[];  // 新轮廓
  direction: { x: number; y: number; z: number };  // 新朝向
}

支持的物品类型

| 类型 | 行为 | 偏移量 | |------|------|--------| | socket(电源) | 贴墙 | 3mm | | switch(开关) | 贴墙 | 3mm | | air conditioner(空调) | 贴墙 | 3mm | | cabinet(柜子) | 贴墙 | 2cm |

字段说明

输入字段

| 字段 | 说明 | 示例 | |------|------|------| | obj_id | 物体的唯一标识符 | "socket_001" | | category | 物体类型 | "socket", "switch", "cabinet" | | box | 包围盒,用于碰撞检测 | { isBox3: true, min: {...}, max: {...} } | | position | 物体中心点坐标 | { x: 1.0, y: 2.0, z: 1.4 } | | size | 物体的长宽高 | { x: 0.085, y: 0.085, z: 0.085 } | | contour | 底面轮廓点数组 | [{x, y, z}, ...] | | direction | 物体的朝向向量 | { x: 0, y: 1, z: 0 } |

输出字段

| 字段 | 变化 | 说明 | |------|------|------| | obj_id | ❌ 不变 | 物体的唯一标识符 | | category | ❌ 不变 | 物体类型 | | box | ❌ 不变 | 原始包围盒 | | size | ❌ 不变 | 原始尺寸 | | position | ✅ 更新 | 吸附后的新位置 | | contour | ✅ 更新 | 吸附后的新轮廓点 | | direction | ✅ 更新 | 墙面内侧方向 | | snapped | ✅ 新增 | 是否成功吸附 | | wallIndex | ✅ 新增 | 吸附到的墙面索引 | | distance | ✅ 新增 | 与墙面的距离 |

前端使用示例

import { snapToWalls } from 'rm-wall-snap-3d';

// 准备数据
const items = [
  {
    obj_id: 'socket_001',
    category: 'socket',
    box: { isBox3: true, min: {...}, max: {...} },
    position: { x: 1.0, y: 2.0, z: 1.4 },
    size: { x: 0.085, y: 0.085, z: 0.085 },
    contour: [...],
    direction: { x: 0, y: 1, z: 0 }
  }
];

const walls = [...]; // 墙体数据

// 调用吸附
const result = snapToWalls({ items, walls });

// 更新前端物体
result.data.results.forEach(item => {
  if (item.snapped) {
    // 更新位置
    object.position.set(item.position.x, item.position.y, item.position.z);
    
    // 更新朝向
    object.rotation.z = Math.atan2(item.direction.y, item.direction.x);
    
    // 使用 contour 绘制 2D 轮廓
    drawContour(item.contour);
  }
});

注意事项

  1. box 和 size 保持不变:用于碰撞检测和显示尺寸
  2. position 更新:吸附后的新中心位置
  3. contour 更新:吸附后的新轮廓点,用于 2D 绘制
  4. direction 更新:指向墙面内侧的方向向量
  5. 坐标系:使用右手坐标系,Z轴向上

License

MIT