

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>多楼层室内定位可视化 Demo(A*避障)</title>
<style>body { margin: 0; overflow: hidden; }#layerControls { position: absolute; top: 10px; left: 10px; z-index: 100; background: rgba(255,255,255,0.9); padding: 10px; border-radius: 5px; }#layerControls button { display: block; margin-bottom: 5px; width: 120px; }
</style>
</head>
<body>
<div id="layerControls"><button data-layer="all">显示全部</button><button data-layer="0">楼层 1</button><button data-layer="1">楼层 2</button><button data-layer="2">楼层 3</button>
</div><script src="https://cdn.jsdelivr.net/npm/three@0.125.2/build/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.125.2/examples/js/controls/OrbitControls.js"></script>
<script>
// ==================== THREE.js 基础 ====================
let scene, camera, renderer, controls;
let floors = [], floorMaterials = [];
let beacons = [], movingPoint;
let gridSize = 40, cellSize = 20; // 栅格参数
let mapGrid = []; // 每层栅格地图
let path = [], pathIndex = 0; // A*路径
let targetBeaconIndex = 0;init();
animate();function init() {scene = new THREE.Scene();scene.background = new THREE.Color(0xf0f0f0);camera = new THREE.PerspectiveCamera(45, window.innerWidth/window.innerHeight, 1, 5000);camera.position.set(500, 800, 1500);renderer = new THREE.WebGLRenderer({antialias:true});renderer.setSize(window.innerWidth, window.innerHeight);document.body.appendChild(renderer.domElement);controls = new THREE.OrbitControls(camera, renderer.domElement);const gridHelper = new THREE.GridHelper(gridSize*cellSize, gridSize, 0x888888, 0xcccccc);scene.add(gridHelper);// 创建楼层for(let i=0;i<3;i++){const floorGroup = new THREE.Group();const width = gridSize*cellSize, height=50, depth=gridSize*cellSize;const geometry = new THREE.BoxGeometry(width, height, depth);const material = new THREE.MeshBasicMaterial({color:0x00ff00, transparent:true, opacity:0.2});floorMaterials.push(material);const mesh = new THREE.Mesh(geometry, material);mesh.position.y = 50 + i*100;floorGroup.add(mesh);const edges = new THREE.EdgesGeometry(geometry);const line = new THREE.LineSegments(edges, new THREE.LineBasicMaterial({color:0x00aa00}));line.position.copy(mesh.position);floorGroup.add(line);scene.add(floorGroup);floors.push(floorGroup);// 生成栅格地图(0通行,1障碍)let layerGrid = Array.from({length:gridSize},()=>Array(gridSize).fill(0));// 随机添加障碍物(墙)for(let w=0; w<60; w++){const x = Math.floor(Math.random()*gridSize);const z = Math.floor(Math.random()*gridSize);layerGrid[x][z] = 1;const wallGeo = new THREE.BoxGeometry(cellSize, 50, cellSize);const wallMat = new THREE.MeshBasicMaterial({color:0x444444});const wall = new THREE.Mesh(wallGeo, wallMat);wall.position.set((x-gridSize/2)*cellSize+cellSize/2, 50 + i*100, (z-gridSize/2)*cellSize+cellSize/2);scene.add(wall);}mapGrid.push(layerGrid);// 蓝牙信标const beaconGeo = new THREE.SphereGeometry(8,12,12);const beaconMat = new THREE.MeshBasicMaterial({color:0x0000ff});for(let b=0;b<5;b++){let bx, bz;do{bx = Math.floor(Math.random()*gridSize);bz = Math.floor(Math.random()*gridSize);}while(layerGrid[bx][bz]===1); // 避开障碍const beacon = new THREE.Mesh(beaconGeo, beaconMat);beacon.position.set((bx-gridSize/2)*cellSize+cellSize/2, 50 + i*100, (bz-gridSize/2)*cellSize+cellSize/2);scene.add(beacon);beacons.push({mesh: beacon, floor: i, gridX: bx, gridZ: bz});}}// 移动小球const pointGeo = new THREE.SphereGeometry(10,16,16);const pointMat = new THREE.MeshBasicMaterial({color:0xff0000});movingPoint = new THREE.Mesh(pointGeo, pointMat);movingPoint.position.set(0, 50, 0);scene.add(movingPoint);setNextTarget();// 楼层按钮document.querySelectorAll('#layerControls button').forEach(btn=>{btn.addEventListener('click',()=>{const layer = btn.getAttribute('data-layer');if(layer==='all'){floors.forEach(f=>f.visible=true);floorMaterials.forEach(m=>m.color.set(0x00ff00));movingPoint.visible = true;beacons.forEach(b=>b.mesh.visible=true);}else{floors.forEach((f,i)=>f.visible=i==layer);floorMaterials.forEach((m,i)=>m.color.set(i==layer?0xffaa00:0x00ff00));movingPoint.position.y = 50 + layer*100;movingPoint.position.x = 0;movingPoint.position.z = 0;movingPoint.visible = true;beacons.forEach(b=>b.mesh.visible=(b.floor==layer));}});});window.addEventListener('resize',()=>{camera.aspect=window.innerWidth/window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight);});
}// ==================== A* 路径规划 ====================
function setNextTarget(){if(beacons.length===0) return;targetBeacon = beacons[targetBeaconIndex % beacons.length];const layerGrid = mapGrid[targetBeacon.floor];path = findPath(layerGrid,gridPos(movingPoint.position.x),gridPos(movingPoint.position.z),targetBeacon.gridX,targetBeacon.gridZ);pathIndex=0;targetBeaconIndex++;
}function gridPos(coord){ return Math.floor((coord + gridSize*cellSize/2)/cellSize); }
function coordPos(grid){ return (grid - gridSize/2)*cellSize + cellSize/2; }function updateMovingPoint(){if(!path || pathIndex>=path.length) {setNextTarget();return;}const target = path[pathIndex];const tx = coordPos(target.x);const tz = coordPos(target.z);const speed = 4;const dx = tx - movingPoint.position.x;const dz = tz - movingPoint.position.z;const dist = Math.sqrt(dx*dx + dz*dz);if(dist<speed){movingPoint.position.x = tx;movingPoint.position.z = tz;pathIndex++;}else{movingPoint.position.x += dx/dist*speed;movingPoint.position.z += dz/dist*speed;}
}// ==================== 简单 A* 算法 ====================
function findPath(grid, startX, startZ, endX, endZ){const openList=[], closedList=[];const nodes = [];for(let x=0;x<gridSize;x++){nodes[x]=[];for(let z=0;z<gridSize;z++){nodes[x][z]={x,z,g:0,h:0,f:0,parent:null,walkable:grid[x][z]===0};}}function heuristic(a,b){ return Math.abs(a.x-b.x)+Math.abs(a.z-b.z); }openList.push(nodes[startX][startZ]);while(openList.length>0){openList.sort((a,b)=>a.f-b.f);const current = openList.shift();closedList.push(current);if(current.x===endX && current.z===endZ) {const ret=[];let c = current;while(c){ ret.push({x:c.x,z:c.z}); c=c.parent; }return ret.reverse();}const dirs=[[1,0],[-1,0],[0,1],[0,-1]];for(const d of dirs){const nx=current.x+d[0], nz=current.z+d[1];if(nx<0||nz<0||nx>=gridSize||nz>=gridSize) continue;const neighbor = nodes[nx][nz];if(!neighbor.walkable || closedList.includes(neighbor)) continue;const g = current.g+1;if(!openList.includes(neighbor) || g<neighbor.g){neighbor.g = g;neighbor.h = heuristic(neighbor,{x:endX,z:endZ});neighbor.f = neighbor.g + neighbor.h;neighbor.parent = current;if(!openList.includes(neighbor)) openList.push(neighbor);}}}return []; // 没路
}// ==================== 动画循环 ====================
function animate(){requestAnimationFrame(animate);updateMovingPoint();renderer.render(scene, camera);controls.update();
}
</script>
</body>
</html>