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

xinrui-3d

v1.1.8

Published

3D scene renderer for floor resources

Readme

xinrui-3d

基于 Three.js 的楼层 3D 场景渲染模块,用于展示编辑器保存的楼层、墙体、区域、普通图标、通行设施和设备图标。

安装

npm install xinrui-3d three vue
import { createScene3D } from 'xinrui-3d'

快速开始

const scene3d = createScene3D(container, floorData, {
  resourceBaseUrl: '/resource/',
  showAreaLabels: true,
  showIconSprites: true,
  showPassageSprites: true,
  showDeviceSprites: true
})

scene3d.on('click', (object, feature) => {
  console.log(feature)
})

组件销毁时释放资源:

scene3d.dispose()

创建场景

createScene3D(
  container: HTMLElement,
  data?: EditorData | FeatureCollection,
  options?: SceneOptions
): TDScene

常用配置:

| 配置 | 类型 | 默认值 | 说明 | | --- | --- | --- | --- | | targetSize | number | 0 | 场景目标尺寸,0 表示按原始比例计算 | | areaHeight | number | 0.08 | 地面区域厚度 | | wallHeight | number | 4 | 墙体高度 | | showAreaLabels | boolean | true | 显示房间文字 | | showIconSprites | boolean | true | 显示普通图标 | | showPassageSprites | boolean | true | 显示通行设施图标 | | showDeviceSprites | boolean | true | 设备图标总开关;关闭后筛选方法也不能显示设备 | | showPaths | boolean | false | 显示路径 | | showFog | boolean | true | 启用远景雾 | | background | number | 0xf7fafc | 背景颜色 | | resourceBaseUrl | string | | 图标资源根路径 | | resourceExtension | string | | 默认资源扩展名 | | resourceFolders | object | | 不同业务图层的资源目录映射 | | resolveResourcePath | function | | 自定义资源路径解析函数 |

数据结构

推荐使用编辑器数据结构:

{
  schemaVersion: 'rt-gis-editor.v1',
  coordinateSystem: {
    metersPerUnit: 1
  },
  baseMap: {
    type: 'FeatureCollection',
    features: []
  },
  business: {
    type: 'FeatureCollection',
    features: []
  }
}

业务点位通过 properties.layerType 区分:

| layerType | 内容 | | --- | --- | | icon | 普通图标 | | device | 设备图标 | | passage / facility | 通行设施 |

设备示例:

{
  type: 'Feature',
  geometry: {
    type: 'Point',
    coordinates: [113.1, 23.1]
  },
  properties: {
    layerType: 'device',
    categoryCode: 'air-conditioner',
    deviceCode: 'AC-001',
    categoryName: '空调',
    markerUrl: '/resource/device/air-conditioner.png'
  }
}

设备显隐

模块不再创建设备分类栏或 Vue 分类组件。设备数据会在初始化时缓存,但设备 Sprite 默认不创建,业务侧通过下面的方法控制显示时才按需创建。

设备类型字段按以下顺序读取:

  1. properties.deviceTypeCode
  2. properties.categoryCode
  3. properties.typeCode
  4. properties.subType

指定类型显示或隐藏

scene3d.showDeviceType(deviceTypeCode: string, visible?: boolean): number
scene3d.hideDeviceType(deviceTypeCode: string): number

showDeviceType 第二个参数控制显示或隐藏,默认 true。返回值是当前实际显示的设备图标数量。

scene3d.showDeviceType('air-conditioner')        // 显示空调类型
scene3d.showDeviceType('camera', true)           // 显示监控类型
scene3d.showDeviceType('air-conditioner', false) // 隐藏空调类型
scene3d.hideDeviceType('camera')                 // 隐藏监控类型

类型显隐支持叠加。例如先显示空调,再显示监控,会同时显示两个类型;隐藏其中一个类型不会影响另一个类型。

全部设备显示或隐藏

scene3d.setAllDeviceVisible(visible?: boolean): number
scene3d.showAllDevices(): number
scene3d.hideAllDevices(): number
scene3d.showAllDevices()
scene3d.hideAllDevices()
scene3d.setAllDeviceVisible(true)
scene3d.setAllDeviceVisible(false)

调用全部显隐方法会清空类型级覆盖状态。传入空字符串也等同于控制全部设备:

scene3d.showDeviceType('', true)  // 等同 showAllDevices()
scene3d.showDeviceType('', false) // 等同 hideAllDevices()

showDeviceSprites: false 时,以上方法都会返回 0,设备图标继续保持隐藏。

性能说明

设备 Sprite 采用按需创建策略:setData() 时只缓存设备 feature,不创建 Three Sprite;调用 showDeviceType()showAllDevices() 时,只为需要显示的设备创建 Sprite。已经创建过的 Sprite 后续会复用,隐藏时只切换 object.visible

这样可以降低首次打开场景的 CPU/GPU 压力,并避免一开始加载所有隐藏设备贴图。首次显示某个设备类型时会有一次创建和贴图加载成本;之后再次显示同一批设备会很快。若调用 showAllDevices() 且设备数量达到上千、图标贴图尺寸较大、或每个设备使用独立图片,仍然会在这次调用时产生集中开销。此类场景建议复用同一类设备贴图、压缩图片尺寸,或按楼层/区域分批传入数据。

运行时图标

新增图标

const sprite = scene3d.createIcon(
  '/resource/device/camera.png',
  [113.1, 23.1],
  {
    layerType: 'device',
    categoryCode: 'camera',
    deviceCode: 'CAM-001'
  }
)

addIcon()createIcon() 的别名。

替换设备图标

scene3d.replaceDeviceIcon(
  '/resource/device/camera-offline.png',
  'CAM-001',
  { status: 'offline' }
)

移动设备图标

scene3d.moveDeviceIcon('CAM-001', [113.2, 23.2], {
  duration: 1000,
  follow: true
})

设备状态

scene3d.setDeviceAlarm('CAM-001')
scene3d.setDeviceOffline('CAM-001')

setDeviceAlarm(deviceCode) 会把指定设备切换为告警状态,内部使用红色状态色重新生成该设备 Sprite,并返回是否处理成功:

setDeviceAlarm(deviceCode: string): boolean
const ok = scene3d.setDeviceAlarm('CAM-001')
if (!ok) {
  console.warn('未找到设备,或设备图标无法更新')
}

setDeviceOffline(deviceCode) 会把指定设备切换为离线状态,内部使用灰色状态色重新生成该设备 Sprite,并返回是否处理成功:

setDeviceOffline(deviceCode: string): boolean
scene3d.setDeviceOffline('CAM-001')

设备查找字段按以下顺序匹配:

  1. properties.deviceCode
  2. properties.deviceId
  3. properties.deviceNo
  4. properties.code
  5. properties.id
  6. feature.id

如果设备 Sprite 还没有被按需创建,状态会先写入缓存的设备 feature;后续调用 showDeviceType()showAllDevices() 显示该设备时,会直接使用更新后的状态。

相机跟随

scene3d.followDevice('CAM-001', {
  distance: 10,
  duration: 800
})

scene3d.stopFollowDevice()

地图与坐标

scene3d.setData(nextFloorData)
scene3d.switchMap(nextFloorData)

const world = scene3d.mapCoordinateToWorld([113.1, 23.1])
const coordinate = scene3d.worldToMapCoordinate(world.x, world.z)

切换地图

switchMap(data, options)setData(data, options) 的语义化别名,用于楼层、底图或业务点位整体切换。

switchMap(data: EditorData | FeatureCollection, options?: SceneOptions): void

调用后会执行完整重建流程:

  1. 清理当前地图、标签、业务图标、路径和动画状态
  2. 重新归一化传入数据
  3. 根据新底图和业务数据重建坐标投影
  4. 渲染新底图、区域标签、非设备业务图标和路径
  5. 缓存设备 feature,设备 Sprite 仍按需创建
  6. 重新适配相机视角
scene3d.switchMap(floor2Data, {
  targetSize: 120,
  showAreaLabels: true,
  showPaths: false
})

切换后之前通过 showDeviceType()showAllDevices() 设置的设备显隐状态会重置;需要按新楼层重新调用:

scene3d.switchMap(floor2Data)
scene3d.showDeviceType('camera')

如果只是新增、移动或替换少量运行时图标,优先使用 createIcon()moveDeviceIcon()replaceDeviceIcon(),不需要调用 switchMap() 整体重建。

TDScene 方法速查

数据与生命周期

setData(data: EditorData | FeatureCollection, options?: SceneOptions): void
switchMap(data: EditorData | FeatureCollection, options?: SceneOptions): void
resize(): void
clear(): void
dispose(): void

图层与设备显隐

showDeviceType(deviceTypeCode: string, visible?: boolean): number
hideDeviceType(deviceTypeCode: string): number
setAllDeviceVisible(visible?: boolean): number
showAllDevices(): number
hideAllDevices(): number

运行时图标

createIcon(image: string | HTMLImageElement | HTMLCanvasElement | THREE.Texture, coordinate: CoordinateLike, info?: object, options?: object): THREE.Sprite | null
addIcon(...args): THREE.Sprite | null
replaceDeviceIcon(image: ImageLike, deviceCode: string, info?: object): boolean
replaceIcon(image: ImageLike, deviceCode: string, info?: object): boolean
moveDeviceIcon(deviceCode: string, endPosition: PositionLike, options?: MoveOptions): boolean
moveIcon(deviceCode: string, endPosition: PositionLike, options?: MoveOptions): boolean

设备状态

setDeviceAlarm(deviceCode: string): boolean
setDeviceOffline(deviceCode: string): boolean
replaceDeviceSpriteColor(deviceCode: string, color: string, status?: string): boolean

查询与坐标

findObjectIndex(deviceCode: string): number
findObject(deviceCode: string): THREE.Object3D | null
mapCoordinateToWorld(coordinate: [number, number]): { x: number, y: number, z: number } | null
worldToMapCoordinate(x: number, z: number): [number, number] | null
resolveRuntimePosition(position: PositionLike, fallbackY?: number): object | null

相机与事件

followDevice(deviceCode: string, options?: FollowOptions): boolean
cameraFollowDevice(deviceCode: string, options?: FollowOptions): boolean
trackDevice(deviceCode: string, options?: FollowOptions): boolean
stopFollowDevice(): void
animateCamera(endPos: THREE.Vector3, endTarget: THREE.Vector3, options?: CameraOptions): boolean
on(type: 'click', callback: (object, feature) => void): (() => void) | null
off(type: 'click'): void

CSS3D 标签

createLabel(component: VueComponent, props?: object, scale?: number): CSS3DSprite

Vue CSS3D 标签

const label = scene3d.createLabel(DeviceLabel, {
  name: '摄像头 1',
  status: '在线'
}, 0.02)

label.position.set(x, y, z)
scene3d.scene.add(label)

该方法只创建 CSS3DSprite,不会自动加入场景。

事件

const cleanup = scene3d.on('click', (object, feature) => {
  console.log(object, feature)
})

cleanup()
// 或
scene3d.off('click')

导出

import {
  TDScene,
  TDMethods,
  createScene3D
} from 'xinrui-3d'

TDMethods 提供数据归一化、坐标投影、Mesh/Sprite 创建、资源路径解析和 Three.js 资源释放等底层能力。