文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

使用three.js 绘制三维带箭头线的详细过程

2024-04-02 19:55

关注

需求:这个需求是个刚需啊!在一个地铁场景里展示逃生路线,这个路线肯定是要有指示箭头的,为了画这个箭头,我花了不少于十几个小时,总算做出来了,但始终有点问题。我对这个箭头的要求是,无论场景拉近还是拉远,这个箭头不能太大,也不能太小看不清,形状不能变化,否则就不像箭头了。

使用到了 three.js 的 Line2.js 和一个开源库MeshLine.js

部分代码:

DrawPath.js:




import * as THREE from '../build/three.module.js';
import { MeshLine, MeshLineMaterial, MeshLineRaycast } from '../js.my/MeshLine.js';

import { Line2 } from '../js/lines/Line2.js';
import { LineMaterial } from '../js/lines/LineMaterial.js';
import { LineGeometry } from '../js/lines/LineGeometry.js';
import { GeometryUtils } from '../js/utils/GeometryUtils.js';

import { CanvasDraw } from '../js.my/CanvasDraw.js';

import { Utils } from '../js.my/Utils.js';
import { Msg } from '../js.my/Msg.js';

let DrawPath = function () {

    let _self = this;

    let _canvasDraw = new CanvasDraw();
    let utils = new Utils();
    let msg = new Msg();

    this._isDrawing = false;
    this._path = [];
    this._lines = [];
    this._arrows = [];

    let _depthTest = true;
    let _side = 0;

    let viewerContainerId = '#cc';
    let viewerContainer = $(viewerContainerId)[0];

    let objects;
    let camera;
    let turn;
    let scene;

    this.config = function (objects_, camera_, scene_, turn_) {
        objects = objects_;
        camera = camera_;
        turn = turn_;
        scene = scene_;

        this._oldDistance = 1;
        this._oldCameraPos = { x: camera.position.x, y: camera.position.y, z: camera.position.z }
    }

    this.start = function () {
        if (!this._isDrawing) {
            this._isDrawing = true;
            viewerContainer.addEventListener('click', ray);
            viewerContainer.addEventListener('mousedown', mousedown);
            viewerContainer.addEventListener('mouseup', mouseup);
        }
        msg.show("请点击地面画线");
    }

    this.stop = function () {
        if (this._isDrawing) {
            this._isDrawing = false;
            viewerContainer.removeEventListener('click', ray);
            viewerContainer.addEventListener('mousedown', mousedown);
            viewerContainer.addEventListener('mouseup', mouseup);
        }
        msg.show("停止画线");
    }

    function mousedown(params) {
        this._mousedownPosition = { x: camera.position.x, y: camera.position.y, z: camera.position.z }
    }

    function mouseup(params) {
        this._mouseupPosition = { x: camera.position.x, y: camera.position.y, z: camera.position.z }
    }

    function ray(e) {
        turn.unFocusButton();

        let raycaster = createRaycaster(e.clientX, e.clientY);
        let intersects = raycaster.intersectObjects(objects.all);
        if (intersects.length > 0) {
            let point = intersects[0].point;

            let distance = utils.distance(this._mousedownPosition.x, this._mousedownPosition.y, this._mousedownPosition.z, this._mouseupPosition.x, this._mouseupPosition.y, this._mouseupPosition.z);

            if (distance < 5) {
                _self._path.push({ x: point.x, y: point.y + 50, z: point.z });

                if (_self._path.length > 1) {
                    let point1 = _self._path[_self._path.length - 2];
                    let point2 = _self._path[_self._path.length - 1];

                    drawLine(point1, point2);
                    drawArrow(point1, point2);
                }
            }
        }
    }

    function createRaycaster(clientX, clientY) {
        let x = (clientX / $(viewerContainerId).width()) * 2 - 1;
        let y = -(clientY / $(viewerContainerId).height()) * 2 + 1;

        let standardVector = new THREE.Vector3(x, y, 0.5);

        let worldVector = standardVector.unproject(camera);

        let ray = worldVector.sub(camera.position).normalize();

        let raycaster = new THREE.Raycaster(camera.position, ray);

        return raycaster;
    }

    this.refresh = function () {
        if (_self._path.length > 1) {
            let distance = utils.distance(this._oldCameraPos.x, this._oldCameraPos.y, this._oldCameraPos.z, camera.position.x, camera.position.y, camera.position.z);
            let ratio = 1;
            if (this._oldDistance != 0) {
                ratio = Math.abs((this._oldDistance - distance) / this._oldDistance)
            }

            if (distance > 5 && ratio > 0.1) {
                console.log("======== DrawPath 刷新 ====================================================")
                for (let i = 0; i < _self._path.length - 1; i++) {
                    let arrow = _self._arrows[i];
                    let point1 = _self._path[i];
                    let point2 = _self._path[i + 1];
                    refreshArrow(point1, point2, arrow);
                }
                this._oldDistance = distance;
                this._oldCameraPos = { x: camera.position.x, y: camera.position.y, z: camera.position.z }
            }
        }
    }

    function drawLine(point1, point2) {
        const positions = [];

        positions.push(point1.x / 50, point1.y / 50, point1.z / 50);
        positions.push(point2.x / 50, point2.y / 50, point2.z / 50);

        let geometry = new LineGeometry();
        geometry.setPositions(positions);

        let matLine = new LineMaterial({
            color: 0x009900,
            linewidth: 0.003, // in world units with size attenuation, pixels otherwise
            dashed: true,
            depthTest: _depthTest,
            side: _side
        });

        let line = new Line2(geometry, matLine);
        line.computeLineDistances();
        line.scale.set(50, 50, 50);

        scene.add(line);
        _self._lines.push(line);

    }

    function drawArrow(point1, point2) {
        let arrowLine = _self.createArrowLine(point1, point2);
        var meshLine = arrowLine.meshLine;

        let canvasTexture = _canvasDraw.drawArrow(THREE, renderer, 300, 100); //箭头

        var material = new MeshLineMaterial({
            useMap: true,
            map: canvasTexture,
            color: new THREE.Color(0x00f300),
            opacity: 1,
            resolution: new THREE.Vector2($(viewerContainerId).width(), $(viewerContainerId).height()),
            lineWidth: arrowLine.lineWidth,
            depthTest: _depthTest,
            side: _side,
            repeat: new THREE.Vector2(1, 1),
            transparent: true,
            sizeAttenuation: 1
        });

        var mesh = new THREE.Mesh(meshLine.geometry, material);
        mesh.scale.set(50, 50, 50);
        scene.add(mesh);
        _self._arrows.push(mesh);

    }

    function refreshArrow(point1, point2, arrow) {
        let arrowLine = _self.createArrowLine(point1, point2);
        var meshLine = arrowLine.meshLine;

        let canvasTexture = _canvasDraw.drawArrow(THREE, renderer, 300, 100); //箭头

        var material = new MeshLineMaterial({
            useMap: true,
            map: canvasTexture,
            color: new THREE.Color(0x00f300),
            opacity: 1,
            resolution: new THREE.Vector2($(viewerContainerId).width(), $(viewerContainerId).height()),
            lineWidth: arrowLine.lineWidth,
            depthTest: _depthTest,
            side: _side,
            repeat: new THREE.Vector2(1, 1),
            transparent: true,
            sizeAttenuation: 1
        });

        arrow.geometry = meshLine.geometry;
        arrow.material = material;

    }

    this.createArrowLine = function (point1, point2) {
        let centerPoint = { x: (point1.x + point2.x) / 2, y: (point1.y + point2.y) / 2, z: (point1.z + point2.z) / 2 };
        let distance = utils.distance(point1.x, point1.y, point1.z, point2.x, point2.y, point2.z);

        var startPos = { x: (point1.x + point2.x) / 2 / 50, y: (point1.y + point2.y) / 2 / 50, z: (point1.z + point2.z) / 2 / 50 }

        let d = utils.distance(centerPoint.x, centerPoint.y, centerPoint.z, camera.position.x, camera.position.y, camera.position.z);
        if (d < 2000) d = 2000;
        if (d > 10000) d = 10000;
        let lineWidth = 100 * d / 4000;
        //console.log("d=", d);

        let sc = 0.035;
        var endPos = { x: startPos.x + (point2.x - point1.x) * sc * d / distance / 50, y: startPos.y + (point2.y - point1.y) * sc * d / distance / 50, z: startPos.z + (point2.z - point1.z) * sc * d / distance / 50 }

        var arrowLinePoints = [];
        arrowLinePoints.push(startPos.x, startPos.y, startPos.z);
        arrowLinePoints.push(endPos.x, endPos.y, endPos.z);

        var meshLine = new MeshLine();
        meshLine.setGeometry(arrowLinePoints);

        return { meshLine: meshLine, lineWidth: lineWidth };
    }

    this.setDepthTest = function (bl) {
        if (bl) {
            _depthTest = true;
            this._lines.map(line => {
                line.material.depthTest = true;
                line.material.side = 0;
            });
            this._arrows.map(arrow => {
                arrow.material.depthTest = true;
                arrow.material.side = 0;
            });
        } else {
            _depthTest = false;
            this._lines.map(line => {
                line.material.depthTest = false;
                line.material.side = THREE.DoubleSide;
            });
            this._arrows.map(arrow => {
                arrow.material.depthTest = false;
                arrow.material.side = THREE.DoubleSide;
            });
        }
    }

    
    this.undo = function () {
        scene.remove(this._lines[this._lines.length - 1]);
        scene.remove(this._arrows[this._arrows.length - 1]);
        _self._path.splice(this._path.length - 1, 1);
        _self._lines.splice(this._lines.length - 1, 1);
        _self._arrows.splice(this._arrows.length - 1, 1);
    }

}

DrawPath.prototype.constructor = DrawPath;

export { DrawPath }

show.js中的部分代码:


let drawPath;

    //绘制线路
    drawPath = new DrawPath();
    drawPath.config(
        objects,
        camera,
        scene,
        turn
    );

    $("#rightContainer").show();
    $("#line-start").on("click", function (event) {
        drawPath.start();
    });
    $("#line-stop").on("click", function (event) {
        drawPath.stop();
    });
    $("#line-undo").on("click", function (event) {
        drawPath.undo();
    });
    $("#line-show").on("click", function (event) {
        drawPath.refresh();
    });
    let depthTest = true;
    $("#line-depthTest").on("click", function (event) {
        if (depthTest) {
            drawPath.setDepthTest(false);
            depthTest = false;
        } else {
            drawPath.setDepthTest(true);
            depthTest = true;
        }
    });

setInterval(() => {
    drawPath && drawPath.refresh();
}, 100);

效果图:

还是有点问题:

虽然这个效果图中,场景拉近,箭头有点大,但是最大大小还是做了控制的,就是这个形状有点问题,可能是视角的问题。

我期望的效果应该是这样的,就是无论从什么角度看,箭头不要变形:

到此这篇关于用 three.js 绘制三维带箭头线要了我半条命的文章就介绍到这了,更多相关three.js 三维带箭头线内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-前端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯