多楼层室内定位可视化 Demo(A*路径避障)

发布于:2025-09-12 ⋅ 阅读:(19) ⋅ 点赞:(0)

<!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>


网站公告

今日签到

点亮在社区的每一天
去签到