用 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 =  Utils();
    let msg =  Msg();

    this._isDrawing = false;
    this._path = [];
    this._lines =this._arrows =this.color = '#00F300';

    this._depthTest = truethis._hide = this._lastRefreshTime =  Date().getTime();

    let _side = 0;

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

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

    let canvasTexture;
    let material;

    this.config =  (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 }

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

        material =  MeshLineMaterial({
            useMap: ,map: canvasTexture,color:  THREE.Color(_self.color),opacity: 1 THREE.Vector2($(viewerContainerId).width(),$(viewerContainerId).height()),lineWidth: 50new THREE.Vector2(1,1),transparent: 
        });
    }

    this.start =  () {
        if (!._isDrawing) {
            ;
            viewerContainer.addEventListener('click''mouseup'this.stop = if (;
            viewerContainer.removeEventListener('click''mouseup' mousedown(params) {
        this._mousedownPosition = mouseup(params) {
        this._mouseupPosition = 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,1)">this._mousedownPosition.z,1)">this._mouseupPosition.x,1)">this._mouseupPosition.y,1)">._mouseupPosition.z);

            if (distance < 5) {
                _self._path.push({ x: point.x,y: point.y + 50if (_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);
                }
            }
        }
    }

     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 =  THREE.Raycaster(camera.position,ray);

        return raycaster;
    }

    this.refresh = new Date().getTime() - this._lastRefreshTime > 200) {
             Date().getTime();
            ) {
                let distance = utils.distance(this._oldCameraPos.x,1)">this._oldCameraPos.y,1)">._oldCameraPos.z,camera.position.x,camera.position.y,camera.position.z);
                let ratio = 1;
                this._oldDistance != 0) {
                    ratio = Math.abs((this._oldDistance - distance) / ._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;
                     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 =  LineGeometry();
        geometry.setPositions(positions);

        geometry.setColors([
            parseInt(_self.color.substr(1,2),16) / 256
        ]);

        let matLine =  LineMaterial({
            color:  in world units with size attenuation,pixels otherwise
            dashed:  $(viewerContainerId).width())
        });

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

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

    }

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

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

    }

     refreshArrow(point1,arrow) {
         meshLine.geometry;
        arrow.material = material;

    }

    this.createArrowLine =  (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.z);
        console.log("d=",d);

        let sc = 0.035var 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);

        let meshLine =  MeshLine();
        meshLine.setGeometry(arrowLinePoints);

         meshLine;
    }

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

    this.getPath = return ._path;
    }

    this.hide =  scene.remove(line));
         scene.remove(arrow));
        ;
    }

    this.show =  scene.add(line));
         scene.add(arrow));
        this.isShow = return !._hide;
    }

    this.create =  (path,color) {
        _self.color = color;
        _self._path = path;

        material = 
        });

        ) {
                let point1 = _self._path[i];
                let point2 = _self._path[i + 1];

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

    this.getDepthTest =  _self._depthTest;
    }

    *
     * 撤销
     */
    this.undo =  () {
        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 =  () {

    *
     * 画文本和气泡
     this.drawText =  (THREE,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 =  THREE.CanvasTexture(canvas);
        canvasTexture.magFilter = THREE.NearestFilter;
        canvasTexture.minFilter = THREE.NearestFilter;

        let maxAnisotropy = renderer.capabilities.getMaxAnisotropy();
        canvasTexture.anisotropy = maxAnisotropy;

         canvasTexture;
    }

    *
     * 画箭头
     this.drawArrow = );

        canvas.width = width;
        canvas.height = height;

        ctx.save();

        ctx.translate(0,0this.drawRoundRectPath(ctx,height,0);

        ctx.fillStyle = "#ffff00";
        ctx.fill();

        this.drawArrowBorder(ctx,2,4,100,96,1)">);
        ctx.fillStyle = "#ffffff";
        ctx.fill();

        ctx.restore();

        let canvasTexture = *
     * 画气泡
     this.drawBubble =  (ctx,x,radius,fillColor) {
        ctx.save();

        ctx.translate(x,y);

        .drawRoundRectPath(ctx,radius);

        ctx.fillStyle = fillColor || "#000";
        ctx.fill();

        this.drawTriangle(ctx,20,40,10,65);
        ctx.fillStyle = fillColor || "#000";
        ctx.fill();

        ctx.restore();
    }

    *
     * 画三角形
     this.drawTriangle = *
     * 画箭头边框
     this.drawArrowBorder = *
     * 画圆角矩形
     this.drawRoundRectPath = 从右下角顺时针绘制,弧度从0到1/2PI  
        ctx.arc(width - radius,height - radius,Math.PI / 2矩形下边线  
        ctx.lineTo(radius,height);

        左下角圆弧,弧度从1/2PI到PI  
        ctx.arc(radius,Math.PI);

        矩形左边线  
        ctx.lineTo(0左上角圆弧,弧度从PI到3/2PI  
        ctx.arc(radius,Math.PI,Math.PI * 3 / 2上边线  
        ctx.lineTo(width - radius,1)">右上角圆弧  
        ctx.arc(width - radius,Math.PI * 3 / 2,Math.PI * 2右边线  
        ctx.lineTo(width,height - radius);

        ctx.closePath();
    }

    *
     * 画圆
     this.drawCircle =  height;

        ctx.save();

        ctx.beginPath(0);

        ctx.arc(width / 2,height / 2,2 * Math.PI);

        ctx.closePath();

        ctx.fillStyle = fillColor || "#000";
        ctx.fill();

        ctx.restore();

        let texture =  THREE.CanvasTexture(canvas);
        texture.needsUpdate = ;

        texture.magFilter = THREE.NearestFilter;
        texture.minFilter = renderer.capabilities.getMaxAnisotropy();
        texture.anisotropy = texture;
    }

}

CanvasDraw.prototype.constructor = CanvasDraw;

export { CanvasDraw }
View Code

show.js中的部分代码:

let drawPath;

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

    $("#rightContainer").show();
    $("#line-start").on("click",1)"> (event) {
        drawPath.start();
    });
    $("#line-stop").on("click",1)"> (event) {
        drawPath.stop();
    });
    $("#line-undo").on("click",1)"> (event) {
        drawPath.undo();
    });
    $("#line-show").on("click",1)"> (event) {
        drawPath.refresh();
    });
    let depthTest = ;
    $("#line-depthTest").on("click",1)"> (event) {
         (depthTest) {
            drawPath.setDepthTest();
            depthTest = ;
        }  {
            drawPath.setDepthTest(;
        }
    });

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

效果图:

还是有点问题:

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

 

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

 

 

 

 

 

 

原文地址:https://www.cnblogs.com/s0611163

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


1.第一步 设置响应头 header(&#39;Access-Control-Allow-Origin:*&#39;); //支持全域名访问,不安全,部署后需要固定限制为客户端网址 header(&#39;Access-Control-Allow-Methods:POST,GET,OPTIONS,D
$.inArray()方法介绍 $.inArray()函数用于在数组中搜索指定的值,并返回其索引值。如果数组中不存在该值,则返回-1; $.inArray(value,array) --value是要查找的值,array是被查找的数组。 有如下实例: &lt;!DOCTYPE html&gt; &l
jquery.serializejson.min.js的妙用 关于这个jquery.serializejson.min.js插件来看,他是转json的一个非常简单好用的插件。 前端在处理含有大量数据提交的表单时,除了使用Form直接提交刷新页面之外,经常碰到的需求是收集表单信息成数据对象,Ajax提
JS 将form表单数据快速转化为object对象(json对象) jaymou 于 2020-03-03 11:11:05 发布 3534 收藏 3 分类专栏: 前端 文章标签: javascript jquery 版权 前端 专栏收录该内容 5 篇文章0 订阅 订阅专栏 直接上代码 /** *
jQuery的区别:$().click()和$(document).on(&#39;click&#39;,&#39;要选择的元素&#39;,function(){})的不同 文章地址:https://www.cnblogs.com/sqh17/p/7746418.html 解决:动态创建的元素的事件
jQuery插件之jquery.spinner数字智能增减插件 参考地址:http://www.helloweba.com/view-blog-282.html 左右加减数字 像京东提交订单时目前使用的是左右加减数字的效果,这个效果直接明了,操作简单。我们使用jquery.spinner.js插件实
layui标签或一般标签均可&lt;div class=&quot;layui-form-item&quot;&gt; &lt;label class=&quot;layui-form-label&quot;&gt;异地仓名称&lt;/label&gt; &lt;div class=&quot;la
网上对于select option 动态添加修改如下, $(&quot;#selectId&quot;).append(&quot;&lt;option value=&#39;&quot;+value+&quot;&#39;&gt;&quot;+text+&quot;&lt;/option&gt;&
jQuery中的 $.extend() 和 $.fn.extend() ANGWH 于 2020-05-24 06:39:59 发布 注意:$.extend是为jQuery类添加添加类方法,用$.调用(类似$.ajax),$.fn.extend则是为jQuery对象添加方法(实例方法),用DOM元素
jquery 循环数组输出显示在html页面 jquery 没有双向数据绑定,但是很多需求确实需要我们从后台接收到数组或者对象循环显示在前台页面上,这时我们可以用字符串拼接,元素添加的方法去实现 js部分如下: 复制代码 $(function(){ var a=[&quot;1aa&quot;,&q
javascript事件委托理解,jQuery .on()方法一步到位实现事件委托 Javascript-概念原理 专栏收录该内容 10 篇文章0 订阅 订阅专栏 本篇文章借鉴自:博客园文章,只为自己巩固下事件委托方面的知识 概述: 什么叫事件委托?他还有一个名字叫做事件代理,(时间代理 事件委托,
JQuery-$.when().done().fail()的使用 原文引用于&#160;Echoo华地于&#160;2022-01-06 14:07:10 发布 jQuery的开发速度很快,几乎每半年一个大版本,每两个月一个小版本。 每个版本都会引入一些新功能。今天我想介绍的,就是从jQuery 1
jQuery tableExport导出 excel 上篇写的是jQuery&#160;导出word,就试试导出excel。看见网上写的很乱,我这就把我写的整理下来,有部分来自网上capy 1. js文件的引用 &lt;script type=&quot;text/javascript&quot;
jQuery的遍历-prev()和next()方法 &lt;div class=&quot;box&quot; id=&quot;box&quot;&gt; &lt;a href=&#39;#&#39; class=&quot;a&quot;&gt; &lt;input type=&quot;tex
attr()和addClass()的区别 方法 addClass() attr()用途&#x9;追加样式&#x9;设置样式对同一个网页元素操作&#x9;&lt;p&gt;test&lt;/p&gt;第1次使用方法&#x9;$(&quot;p&quot;).addClass(&quot;high&quot;);&#x9;$(&quot;p&
前端——函数(匿名函数、自执行函数) FreshLemon_ 于 2019-06-11 17:11:49 发布 函数声明:function box(){} 函数表达式:var box = function(){}; 匿名函数:function(){} (属于函数表达式) 1声明了一个函数: var
js: 获取标签元素data-*属性值的方法 彭世瑜 于 2022-05-23 09:59:50 发布 2165 收藏 1 文章标签: javascript 前端 jquery 版权 标签上有两个属性data-id 和 data-user-name, 需要通过js去获取 &lt;style&gt;
JavaScript函数详解:匿名函数、具名函数、函数传参、不定参、返回值、JS预解析机制 1.具名函数 定义: 调用: 方式1:方法名(); 可以多次调用 方式2:在事件中调用,直接写函数名,不需用括号 2.匿名函数 没有名字的函数 匿名函数在使用时只有两种情况: 1.匿名函数自执行:声明后不需要
如何等待ajax完成再执行相应操作 ajax广泛应用于异步请求,对于大多数业务来说,这是十分方便的,但对于一些特殊的业务,ajax的异步性会起到相反的作用。 例如在ajax请求成功后,后续的操作需要依赖ajax执行成功后的相应操作。 // 声明一个表示状态的全局变量 status var statu
一步一步教你写一个jQuery的插件教程(Plugin) 更新时间:2009年09月03日 02:10:54 作者: 我将会在下面的例子中一个一个的说明上面这几个条件,做完这些事情后我们就会创建一个高亮显示text的简单插件。 jQuery 的plugin开发需要注意的事情, 1. 明确jQuery