Skip to content

三维渲染

BicMap 通过内置的 URDFPlugin 支持在地图上叠加 URDF 格式的机器人三维模型,并提供三维空间标注(3D Marker)能力,适用于数字孪生、机器人可视化等场景。

源码参考

  • 核心类:src/bicMap/core/urdf/ — URDFPlugin
  • 示例:src/examples/indoor/load3D/index.vue
  • 示例:src/examples/indoor/load3dControl/index.vue
  • 示例:src/examples/indoor/load3dMarker/index.vue

URDFPlugin — URDF 机器人模型

URDFPlugin 基于 Three.js 实现,在透明 Canvas 上渲染机器人三维模型,并通过屏幕坐标投影与 MapLibre 地图实时同步位置和相机视角。

创建实例

js
import URDFPlugin from '@x-humanoid-cloud/bic-map/urdf'

const urdfPlugin = new URDFPlugin(
  container,    // HTMLElement — 承载 Three.js 渲染的 DOM 容器
  sceneOptions, // null | { background: color } — 传 null 为透明背景
  cameraOptions,// { fov, position: { x, y, z } }
  controlsOptions, // { controlsFlag: boolean } — false 禁用内置 OrbitControls
  pixelRatio    // number — 设备像素比,默认 1
)

cameraOptions

参数类型说明
fovnumber相机视角(Field of View),推荐 15–30
position.xnumber相机 X 轴位置
position.ynumber相机 Y 轴位置
position.znumber相机 Z 轴位置

加载 URDF 模型

js
const result = await urdfPlugin.urdfLoad(urdfUrl, packages)
// result.robot — Three.js Object3D,即 URDF 根节点
参数类型说明
urdfUrlstringURDF 文件路径
packagesObjectpackage:// 路径映射,纯 primitive 模型可传 {}

实例方法

js
// 更新球坐标相机(与地图视角同步)
// azimuth: 方位角(弧度),elevation: 仰角(弧度),distance: 距离(米)
urdfPlugin.updateCameraControl(azimuth, elevation, distance)

// 设置相机是否自动注视机器人身体中心
urdfPlugin.setLookAtRobot(boolean)

// 添加方向光
urdfPlugin.addDirectionalLight(color, intensity, x, y, z)

// 添加环境光
urdfPlugin.addAmbientLight(color, intensity)

// 容器尺寸变化时调用,刷新渲染器
urdfPlugin.onResize()

// 销毁实例,释放 Three.js 资源
urdfPlugin.destroy()

与地图视角同步

MapLibre pitch(俯视角度)需转换为 Three.js 球坐标仰角(elevation):

js
// pitch=0(垂直俯视) → elevation=π/2;pitch=90(水平) → elevation=0
function mapPitchToElevation(pitchDeg) {
  return (90 - pitchDeg) * Math.PI / 180
}

// 在 map.on('move') 中实时同步
map.on('move', () => {
  const el = mapPitchToElevation(map.getPitch())
  const az = Math.PI / 2 + map.getBearing() * Math.PI / 180
  urdfPlugin.updateCameraControl(az, el, CAM_DISTANCE)
})

GPS 坐标 → 屏幕位置

将机器人的经纬度投影到像素坐标,动态更新 Three.js 容器位置,实现模型随地图平移/缩放跟随:

js
function syncRobotScreenPos(lngLat) {
  const px = map.project(lngLat)         // { x, y } 屏幕像素坐标
  urdfLayer.style.left = `${px.x}px`
  urdfLayer.style.top  = `${px.y}px`
  // 容器设置 transform: translate(-50%, -50%) 使中心对齐到坐标点
}

map.on('move', () => syncRobotScreenPos(robotLngLat))

完整使用示例

vue
<template>
  <div style="position: relative; width: 100%; height: 100vh;">
    <div id="mapContainer" style="position: absolute; inset: 0;" />
    <!-- Three.js 覆盖层,pointer-events: none 透传鼠标事件 -->
    <div
      id="urdfLayer"
      style="position: absolute; width: 240px; height: 320px;
             transform: translate(-50%, -50%); pointer-events: none;"
    />
  </div>
</template>

<script setup>
import { onMounted, onBeforeUnmount } from 'vue'
import bicMap from '@x-humanoid-cloud/bic-map'
import URDFPlugin from '@x-humanoid-cloud/bic-map/urdf'

const CAM_DISTANCE = 3
const PITCH_3D     = 60
let map, urdfPlugin

function mapPitchToElevation(p) { return (90 - p) * Math.PI / 180 }

onMounted(async () => {
  await bicMap.init()
  map = bicMap.createMap({ container: 'mapContainer', zoom: 18, pitch: PITCH_3D })

  map.on('load', async () => {
    // 加载 SLAM 底图
    await bicMap.loadSlamMap(map, { /* SLAM 参数 */ })

    // 创建 URDFPlugin
    const container = document.getElementById('urdfLayer')
    urdfPlugin = new URDFPlugin(
      container,
      null,                    // 透明背景
      { fov: 20, position: { x: 0, y: 2, z: 3 } },
      { controlsFlag: false }, // 由地图相机驱动
      window.devicePixelRatio
    )
    urdfPlugin.addDirectionalLight(0xffffff, 1.5, 5, 10, 5)
    urdfPlugin.addAmbientLight(0xffffff, 0.8)

    const { robot } = await urdfPlugin.urdfLoad('/assets/robot.urdf')
    robot.rotation.x = -Math.PI / 2   // ROS z-up → Three.js y-up
    robot.scale.set(0.2, 0.2, 0.2)

    // 与地图同步
    map.on('move', () => {
      const el = mapPitchToElevation(map.getPitch())
      const az = Math.PI / 2 + map.getBearing() * Math.PI / 180
      urdfPlugin.updateCameraControl(az, el, CAM_DISTANCE)

      const px = map.project([116.4074, 39.9042])
      const layer = document.getElementById('urdfLayer')
      layer.style.left = `${px.x}px`
      layer.style.top  = `${px.y}px`
    })
  })
})

onBeforeUnmount(() => {
  map?.off('move')
  map?.remove()
  urdfPlugin?.destroy()
})
</script>

三维空间数据渲染

在室内 SLAM 地图上叠加三维空间标注(语义区域、设备位置、信息面板等),通过 MapLibre fill-extrusion 图层实现带高度的三维要素渲染。

源码参考src/examples/indoor/load3dMarker/index.vuesrc/examples/indoor/load3dMarker/SpatialInfoPanel.vue

添加三维要素图层

js
// 在 map.on('load') 中添加 fill-extrusion 图层
map.addSource('spatial-data', {
  type: 'geojson',
  data: {
    type: 'FeatureCollection',
    features: [
      {
        type: 'Feature',
        geometry: {
          type: 'Polygon',
          coordinates: [[[lng1,lat1],[lng2,lat2],[lng3,lat3],[lng4,lat4],[lng1,lat1]]]
        },
        properties: {
          height: 3.0,       // 三维高度(米)
          base: 0,           // 底部高度
          color: '#3b82f6',  // 填充颜色
          label: '会议室 A'
        }
      }
    ]
  }
})

map.addLayer({
  id: 'spatial-extrusion',
  type: 'fill-extrusion',
  source: 'spatial-data',
  paint: {
    'fill-extrusion-color': ['get', 'color'],
    'fill-extrusion-height': ['get', 'height'],
    'fill-extrusion-base': ['get', 'base'],
    'fill-extrusion-opacity': 0.75
  }
})

相机倾斜展示三维效果

js
// 加载完成后开启 3D 视角
map.on('load', () => {
  map.easeTo({ pitch: 55, bearing: -15, duration: 800 })
})

在线示例

https://bicmap-ex.x-humanoid-cloud.com//indoor/load3D 在新窗口中打开 ↗
https://bicmap-ex.x-humanoid-cloud.com//indoor/load3dControl 在新窗口中打开 ↗
https://bicmap-ex.x-humanoid-cloud.com//indoor/load3dMarker 在新窗口中打开 ↗