zoukankan      html  css  js  c++  java
  • 用 three.js 绘制三维带箭头线

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

        使用到了 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 = [];
        this.color = '#00F300';
    
        this._depthTest = true;
        this._hide = false;
    
        let _side = 0;
    
        let viewerContainerId = '#threeCanvas';
        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);
            }
        }
    
        this.stop = function () {
            if (this._isDrawing) {
                this._isDrawing = false;
                viewerContainer.removeEventListener('click', ray);
                viewerContainer.removeEventListener('mousedown', mousedown);
                viewerContainer.removeEventListener('mouseup', mouseup);
            }
        }
    
        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 objs = [];
            objects.all.map(object => {
                if (object.material.visible) {
                    objs.push(object);
                }
            });
            let intersects = raycaster.intersectObjects(objs);
            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);
    
            geometry.setColors([
                parseInt(_self.color.substr(1, 2), 16) / 256,
                parseInt(_self.color.substr(3, 2), 16) / 256,
                parseInt(_self.color.substr(5, 2), 16) / 256,
                parseInt(_self.color.substr(1, 2), 16) / 256,
                parseInt(_self.color.substr(3, 2), 16) / 256,
                parseInt(_self.color.substr(5, 2), 16) / 256
            ]);
    
            let matLine = new LineMaterial({
                color: new THREE.Color(_self.color),
                line 0.003, // in world units with size attenuation, pixels otherwise
                dashed: false,
                depthTest: _self._depthTest,
                side: _side,
                vertexColors: THREE.VertexColors
            });
    
            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) {
            var meshLine = _self.createArrowLine(point1, point2);
    
            let canvasTexture = _canvasDraw.drawArrow(THREE, renderer, 300, 100); //箭头
    
            var material = new MeshLineMaterial({
                useMap: true,
                map: canvasTexture,
                color: new THREE.Color(_self.color),
                opacity: 1,
                resolution: new THREE.Vector2($(viewerContainerId).width(), $(viewerContainerId).height()),
                lineWidth: 50,
                depthTest: _self._depthTest,
                side: _side,
                repeat: new THREE.Vector2(1, 1),
                transparent: true,
                sizeAttenuation: 0
            });
    
            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) {
            var meshLine = _self.createArrowLine(point1, point2);
    
            let canvasTexture = _canvasDraw.drawArrow(THREE, renderer, 300, 100); //箭头
    
            var material = new MeshLineMaterial({
                useMap: true,
                map: canvasTexture,
                color: new THREE.Color(_self.color),
                opacity: 1,
                resolution: new THREE.Vector2($(viewerContainerId).width(), $(viewerContainerId).height()),
                lineWidth: 50,
                depthTest: _self._depthTest,
                side: _side,
                repeat: new THREE.Vector2(1, 1),
                transparent: true,
                sizeAttenuation: 0
            });
    
            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);
            //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;
        }
    
        this.setDepthTest = function (bl) {
            if (bl) {
                _self._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 {
                _self._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.getPath = function () {
            return this._path;
        }
    
        this.hide = function () {
            this._lines.map(line => scene.remove(line));
            this._arrows.map(arrow => scene.remove(arrow));
            this._hide = true;
        }
    
        this.show = function () {
            this._lines.map(line => scene.add(line));
            this._arrows.map(arrow => scene.add(arrow));
            this._hide = false;
        }
    
        this.isShow = function () {
            return !this._hide;
        }
    
        this.create = function (path, color) {
            _self.color = color;
            _self._path = path;
    
            if (_self._path.length > 1) {
                for (let i = 0; i < _self._path.length - 1; i++) {
                    let point1 = _self._path[i];
                    let point2 = _self._path[i + 1];
    
                    drawLine(point1, point2);
                    drawArrow(point1, point2);
                }
            }
        }
    
        this.getDepthTest = function () {
            return _self._depthTest;
        }
    
        /**
         * 撤销
         */
        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 }
    View Code

    CanvasDraw.js:

    /**
     * canvas绘图
     */
    
    let CanvasDraw = function () {
    
        /**
         * 画文本和气泡
         */
        this.drawText = function (THREE, renderer, text, width) {
            let canvas = document.createElement("canvas");
            let ctx = canvas.getContext('2d');
    
            canvas.width = width * 2;
            canvas.height = width * 2;
    
            this.drawBubble(ctx, width - 10, width - 65, width, 45, 6, "#00c864");
    
            //设置文字
            ctx.fillStyle = "#ffffff";
            ctx.font = '32px 宋体';
            ctx.fillText(text, width - 10 + 12, width - 65 + 34);
    
            let canvasTexture = new THREE.CanvasTexture(canvas);
            canvasTexture.magFilter = THREE.NearestFilter;
            canvasTexture.minFilter = THREE.NearestFilter;
    
            let maxAnisotropy = renderer.capabilities.getMaxAnisotropy();
            canvasTexture.anisotropy = maxAnisotropy;
    
            return canvasTexture;
        }
    
        /**
         * 画箭头
         */
        this.drawArrow = function (THREE, renderer, width, height) {
            let canvas = document.createElement("canvas");
            let ctx = canvas.getContext('2d');
    
            canvas.width = width;
            canvas.height = height;
    
            ctx.save();
    
            ctx.translate(0, 0);
    
            //this.drawRoundRectPath(ctx, width, height, 0);
    
            //ctx.fillStyle = "#ffff00";
            //ctx.fill();
    
            this.drawArrowBorder(ctx, 2, 0, 0, 4, 100, 50, 0, 96, 2, 100, 300, 50);
            ctx.fillStyle = "#ffffff";
            ctx.fill();
    
            ctx.restore();
    
            let canvasTexture = new THREE.CanvasTexture(canvas);
            canvasTexture.magFilter = THREE.NearestFilter;
            canvasTexture.minFilter = THREE.NearestFilter;
    
            let maxAnisotropy = renderer.capabilities.getMaxAnisotropy();
            canvasTexture.anisotropy = maxAnisotropy;
    
            return canvasTexture;
        }
    
        /**
         * 画气泡
         */
        this.drawBubble = function (ctx, x, y, width, height, radius, fillColor) {
            ctx.save();
    
            ctx.translate(x, y);
    
            this.drawRoundRectPath(ctx, width, height, radius);
    
            ctx.fillStyle = fillColor || "#000";
            ctx.fill();
    
            this.drawTriangle(ctx, 20, height, 40, height, 10, 65);
            ctx.fillStyle = fillColor || "#000";
            ctx.fill();
    
            ctx.restore();
        }
    
        /**
         * 画三角形
         */
        this.drawTriangle = function (ctx, x1, y1, x2, y2, x3, y3) {
            ctx.beginPath();
    
            ctx.moveTo(x1, y1);
            ctx.lineTo(x2, y2);
            ctx.lineTo(x3, y3);
    
            ctx.closePath();
        }
    
        /**
         * 画箭头边框
         */
        this.drawArrowBorder = function (ctx, x1, y1, x2, y2, x3, y3, x4, y4, x5, y5, x6, y6) {
            ctx.beginPath();
    
            ctx.moveTo(x1, y1);
            ctx.lineTo(x2, y2);
            ctx.lineTo(x3, y3);
            ctx.lineTo(x4, y4);
            ctx.lineTo(x5, y5);
            ctx.lineTo(x6, y6);
    
            ctx.closePath();
        }
    
        /**
         * 画圆角矩形
         */
        this.drawRoundRectPath = function (ctx, width, height, radius) {
            ctx.beginPath(0);
    
            //从右下角顺时针绘制,弧度从0到1/2PI  
            ctx.arc(width - radius, height - radius, radius, 0, Math.PI / 2);
    
            //矩形下边线  
            ctx.lineTo(radius, height);
    
            //左下角圆弧,弧度从1/2PI到PI  
            ctx.arc(radius, height - radius, radius, Math.PI / 2, Math.PI);
    
            //矩形左边线  
            ctx.lineTo(0, radius);
    
            //左上角圆弧,弧度从PI到3/2PI  
            ctx.arc(radius, radius, radius, Math.PI, Math.PI * 3 / 2);
    
            //上边线  
            ctx.lineTo(width - radius, 0);
    
            //右上角圆弧  
            ctx.arc(width - radius, radius, radius, Math.PI * 3 / 2, Math.PI * 2);
    
            //右边线  
            ctx.lineTo(width, height - radius);
    
            ctx.closePath();
        }
    
        /**
         * 画圆
         */
        this.drawCircle = function (THREE, renderer, width, height, radius, fillColor) {
            let canvas = document.createElement("canvas");
            let ctx = canvas.getContext('2d');
    
            canvas.width = width;
            canvas.height = height;
    
            ctx.save();
    
            ctx.beginPath(0);
    
            ctx.arc(width / 2, height / 2, radius, 0, 2 * Math.PI);
    
            ctx.closePath();
    
            ctx.fillStyle = fillColor || "#000";
            ctx.fill();
    
            ctx.restore();
    
            let texture = new THREE.CanvasTexture(canvas);
            texture.needsUpdate = true;
    
            texture.magFilter = THREE.NearestFilter;
            texture.minFilter = THREE.NearestFilter;
    
            let maxAnisotropy = renderer.capabilities.getMaxAnisotropy();
            texture.anisotropy = maxAnisotropy;
    
            return texture;
        }
    
    }
    
    CanvasDraw.prototype.constructor = CanvasDraw;
    
    export { CanvasDraw }
    View Code

    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);
    View Code

    效果图:

    还是有点问题:

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

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

     

     

     

  • 相关阅读:
    INF文件的语法解说转
    VC的拨号上网程序
    sql修复
    flash media server 2 下载 + 无限制序列号
    [原] ASPNET2.0中如何历遍GRIDVIEW
    [net2.0] ASPNET内置安全认证架构 的灵异问题~~~~
    SnagIt的Visual Studio Team System插件
    [翻译]使用HtmlAgilityPack更好的HTML分析和验证
    哪些自动化测试工具支持AJAX
    VSTT Rosario CTP
  • 原文地址:https://www.cnblogs.com/s0611163/p/15488246.html
Copyright © 2011-2022 走看看