Iridescent ripples of a bright blue and pink liquid

3D 室内地图

github 地址:three-indoor-map

技术栈:Vue.js | Three.js | Typescript 等

商场室内地图,商场后台管理系统内的核心模块,为商场运营人员提供客行分析服务,主要功能包括以下内容:

  • 地图加载(多层 3D 地图 / 单层 2D 地图)

  • 图片文字精灵

  • 行为轨迹动画

  • 热力图

  • 闪点动画

  • 地图标注 / 编辑

技术细节

场景树优化

空间结构分割

使用包围盒层次,来组织商场的店铺和楼层;把每层楼划分为包围盒,构建出一个层次结构,这样可以快速剔除不可见的楼层和店铺,提高渲染和交互的性能。

步骤:

1、构建视锥体:根据相机的位置、视野角度、近裁剪面和远裁剪面等参数,构建相机的视锥体。视锥体通常通过六个面来定义,包括近裁剪面、远裁剪面和四个侧面。

2、对象包围盒检测:对场景中的每个物体,使用其包围盒(边界框)来检测是否与视锥体相交。包围盒是一个简单的几何形状,用于粗略表示物体的边界范围。

3、视锥体剔除:根据包围盒和视锥体的相交关系,判断物体是否位于视锥体内部。如果一个物体的包围盒与视锥体不相交,那么可以确定该物体完全位于视野外,可以将其剔除。

4、可见物体渲染:只有通过视锥体剔除的物体才会被认为是可见的,它们将进入渲染管线进行后续的光栅化、着色和投影等操作。

层级加载

将不同楼层分批下载,根据视野范围动态加载和卸载楼层数据,只加载当前楼层及附近楼层的店铺信息,减少内存占用和加载时间,提高场景响应速度;

数据压缩和优化

商场地图的数据进行压缩和优化,以减少存储空间和加载时间。可以使用合适的压缩算法对地图数据进行压缩,并优化数据结构,以提高数据读取和处理的效率。

异步加载和级别细节

结合异步加载和级别细节的技术,根据观察者的位置和需要,动态加载不同细节级别的模型和纹理。可以根据观察者的距离和视野范围,选择加载低细节的模型和纹理,以减少渲染开销。

轨迹动画

核心思路

1、创建几何体:表示运动的物体

const geometry = new THREE.SphereGeometry(1, 32, 32);
const material = new THREE.MeshBasicMaterial({ color: 0xff0000 });
const sphere = new THREE.Mesh(geometry, material);

scene.add(sphere);

2、定义数组存储坐标点(这些坐标点将用于定义动画的路径)

const points = [
	new THREE.Vector3(0, 0, 0),
	new THREE.Vector3(2, 0, 2),
	new THREE.Vector3(0, 2, 4),
	new THREE.Vector3(-2, 0, 2),
	new THREE.Vector3(0, -2, 0),
];

3、创建 Tween.js 的动画对象

const tween = new TWEEN.Tween(sphere.position)
	.to(points[points.length - 1], 5000) // 结束值和持续时间
	.onUpdate(() => {
		// 更新物体的位置
		sphere.position.copy(sphere.position);
	})
	.start();

4、创建一个更新函数,在每一帧中更新动画对象

function animate() {
	requestAnimationFrame(animate);
	TWEEN.update();
	renderer.render(scene, camera);
}

animate();

优化

WebWorker:数据分块处理、数据压缩算法(减小传输数据量)

数据分块

将大的数据集分割成较小的块,每次将一个块发送给 Web Worker 进行处理。这样可以避免一次性发送大量数据,减少数据传输的延迟和开销;

在 Web Worker 内部,对每个块进行处理,并返回处理后的结果。主线程接收到结果后,可以根据需要将多个块的结果合并在一起;

数据压缩

Gzip 算法;在主线程里压缩数据,发送压缩后的数据给 WebWorker;在 WebWorker 内部进行解压缩处理;

三维坐标转换二维坐标

通过 Projector 对象的 projectVector 方法(3 → 2)

// 创建一个 Projector 对象
const projector = new THREE.Projector();

// 定义一个三维坐标
const position = new THREE.Vector(x, y, z);

// 使用 projectVector 方法将三维坐标转换为二维坐标
const screenPosition = projector.projectVector(position, camera);

通过 Vector3 对象的 project 方法(3 → 2)

const position = new THREE.Vector3(x, y, z);
position.project(camera);

使用 Raycaster 对象,根据屏幕上的二维坐标发射一条射线,并检测射线与与场景中的物体是否相交,从而确定三维坐标(2 → 3)

碰撞检测

给每个店铺模型定义一个碰撞体积,AABB 盒子,较大的碰撞体积表示这家店铺更大,更重要,需要在更远的距离显示详细信息;

碰撞检测:在每个帧中,使用碰撞检测算法(包围盒/Ammo.js),检测观察者和店铺模型的碰撞关系;

根据检测结果,决定是否显示店铺的详细信息;