threejs 全景图实现添加标记

2,377 阅读1分钟

shjahs.gif activePoint变量为threejs世界坐标,获取到可以传给后端做永久保存。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        body {
            margin: 0;
        }
    </style>
</head>
<body>
<div id="WebGl-output"></div>
<span id="tip" style="position: absolute;color:red;top:0;left:0;">点击右键添加标注</span>
<script src="three.js"></script>
<script src="OrbitControls.js"></script>
<script>
    let doc = document
    let tags = []
    let tagObject = new THREE.Object3D()
    let screenVector = new THREE.Vector3()
    let raycaster = new THREE.Raycaster()
    let vector = new THREE.Vector3();
    let activePoint = null

    let data = [
        {
            "title": "test1",
            "point": {
                "x": -49.12874919105969,
                "y": -7.4075448683018,
                "z": -33.345400819742046
            }
        },
        {
            "title": "test2",
            "point": {
                "x": -29.14179077971636,
                "y": -11.729570788835838,
                "z": -50.88589614945478
            }
        },
        {
            "title": "test3",
            "point": {
                "x": -34.97837170750319,
                "y": -0.4862641623377102,
                "z": 48.69562416852437
            }
        },
        {
            "title": "test4",
            "point": {
                "x": -57.499764366164065,
                "y": 16.241197387208377,
                "z": -2.6968122977083366
            }
        }
    ]

    /**
     * 禁用右键
     **/
    window.oncontextmenu = function (e) {
        e.preventDefault();
    }

    let scene = new THREE.Scene()

    let camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 1000)
    camera.position.set(0, 0, 0.1);

    let renderer = new THREE.WebGLRenderer()
    renderer.setPixelRatio(window.devicePixelRatio)
    renderer.setSize(window.innerWidth, window.innerHeight)
    doc.getElementById('WebGl-output').appendChild(renderer.domElement)

    let geometry = new THREE.SphereGeometry(60, 40, 40)
    geometry.scale(1, 1, -1)

    let texture = new THREE.TextureLoader().load('snow.jpg')
    let material = new THREE.MeshBasicMaterial({
        map: texture,
        color: '#ffffff',
        // side: 2
    })

    let mesh = new THREE.Mesh(geometry, material)
    scene.add(mesh)

    let controls = new THREE.OrbitControls(camera, renderer.domElement)
    controls.rotateSpeed = -0.25;

    scene.add(tagObject);

    let div = doc.getElementById('tip')

    /**
     * 创建json数据
     **/
    data.map(item => {
        sign(item.point, item.title)
    })

    /**
     * 鼠标移动触发
     **/
    function onMouseMove(event) {
        // 屏幕坐标转标准设备坐标
        vector.set((event.clientX / window.innerWidth) * 2 - 1, -(event.clientY / window.innerHeight) * 2 + 1, 0);
        // 将标准设备坐标转为世界坐标
        vector.unproject(camera);

        raycaster = new THREE.Raycaster(camera.position, vector.sub(camera.position).normalize());

        let intersects = raycaster.intersectObjects([mesh]);

        if (intersects.length > 0) {
            activePoint = intersects[0].point;
            let point = toScreenPosition({point: activePoint})
            div.style.left = point.x + 'px';
            div.style.top = point.y + 20 + 'px';
        }
    }

    /**
     * 鼠标点击触发
     **/
    function onMouseDown(event) {
        if (event.buttons === 2) {
            console.log(JSON.stringify(activePoint))  // 传给后台的参数
            sign(activePoint)
        }
    }

    /**
     * 标记
     **/
    function sign(position, title) {
        let tagMesh = new THREE.Mesh(new THREE.SphereGeometry(1), new THREE.MeshBasicMaterial({color: '#ffff00'}));
        tagMesh.position.copy(position);
        tagObject.add(tagMesh);

        let tagElement = doc.createElement("div");
        tagElement.innerHTML = `<span>${!title ? '标记' + (tags.length + 1) : title}</span>`;
        tagElement.style.background = "red";
        tagElement.style.position = "absolute";

        tagMesh.updateTag = function () {
            // 超出相机视野则隐藏
            if (isOffScreen(tagMesh, camera)) {
                tagElement.style.display = "none";
            } else {
                let position = toScreenPosition({obj: tagMesh});
                tagElement.style.display = "block";
                tagElement.style.left = `${position.x - tagElement.offsetWidth / 2}px`;
                tagElement.style.top = `${position.y + 12}px`;
            }
        }
        tagMesh.updateTag();
        doc.getElementById("WebGl-output").appendChild(tagElement);
        tags.push(tagMesh);
    }

    /**
     * 世界坐标转屏幕坐标(3D -> 2D)
     **/
    function toScreenPosition({obj = null, point = null}) {
        point ? screenVector.set(...point) : screenVector.set();
        // 屏幕坐标系中心
        let widthHalf = renderer.getContext().canvas.width / 2;
        let heightHalf = renderer.getContext().canvas.height / 2;

        if (obj) {
            // 更新物体及其后代的全局变换
            obj.updateMatrixWorld();
            // 提取位置相关的分量
            screenVector.setFromMatrixPosition(obj.matrixWorld);
        }

        // 世界坐标转标准设备坐标。范围[-1,1]
        screenVector.project(camera)

        //标准设备坐标转屏幕坐标(2D)
        screenVector.x = screenVector.x * widthHalf + widthHalf;
        screenVector.y = -screenVector.y * heightHalf + heightHalf;

        return {
            x: screenVector.x,
            y: screenVector.y
        };
    }

    /**
     * 判断是否超出相机视野
     **/
    function isOffScreen(obj) {
        let frustum = new THREE.Frustum(); //Frustum用来确定相机的可视区域
        let cameraViewProjectionMatrix = new THREE.Matrix4();
        cameraViewProjectionMatrix.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse); //获取相机的法线
        frustum.setFromProjectionMatrix(cameraViewProjectionMatrix); //设置frustum沿着相机法线方向

        return !frustum.intersectsObject(obj);
    }

    doc.addEventListener("pointerdown", onMouseDown, false);
    doc.addEventListener("pointermove", onMouseMove, false);

    render()

    function render() {
        requestAnimationFrame(render)
        controls.update();
        tags.forEach(function (tagMesh) {
            tagMesh.updateTag();
        });

        renderer.render(scene, camera)
    }

</script>
</body>
</html>

代码修改自 github.com/heavis/thre…