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

使用到了 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; this._lastRefreshTime = new 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 = 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 } canvasTexture = _canvasDraw.drawArrow(THREE, renderer, 300, 100); //箭头 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
});
} 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 (new Date().getTime() - this._lastRefreshTime > 200) {
this._lastRefreshTime = new Date().getTime();
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),
linewidth: 0.0015, // in world units with size attenuation, pixels otherwise
dashed: false,
depthTest: _self._depthTest,
side: _side,
vertexColors: THREE.VertexColors,
resolution: new THREE.Vector2(1, $(viewerContainerId).height() / $(viewerContainerId).width())
}); 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); 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); 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); let 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; 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
}); 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 }

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 }

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 绘制三维带箭头线的更多相关文章

  1. OpenLayer——绘制带箭头的线

    绘制带箭头的线,计算相对复杂,多少是有点影响性能了.更简单的做法:初始.目标点用不同的点进行强调即可. <!DOCTYPE html> <html lang="en&quo ...

  2. 如何利用百度地图JSAPI画带箭头的线?

    百度地图JSAPI提供两种绘制多折线的方式,一种是已知多折线经纬度坐标串通过AddOverlay接口进行添加:另一种是通过在地图上鼠标单击进行绘制(鼠标绘制工具条库).目前这两种方式只能绘制多折线,并 ...

  3. AE常用代码(标注要素、AE中画带箭头的线、如何获得投影坐标、参考坐标、投影方式、FeatureCount注意事项)

    手上的电脑已经用了将近三年了,想入手一台Surface Pro,所以计划着把电脑上的资料整理下,部分资料打算发到博客上来,资料有同事.也有自己的.也有来自网络的,来源途径太多,也没法详细注明,请见谅! ...

  4. iOS_绘制带删除线的Label

    效果图例如以下: 一个带删除线的文本标签,继承自UILabel 自绘代码过程例如以下: 1,重写控件的drawRect方法 2,首先得到上下文对象 3,设置颜色,并指定是填充(Fill)模式还是笔刷( ...

  5. Asp.Net实现JS前台带箭头的流程图方法总结!(个人笔记,信息不全)

    Asp.Net实现JS前台带箭头的流程图方法总结!(持续更新中) 一.返回前台json格式 json5 = "[{\"Id\":2259,\"Name\&quo ...

  6. 如何在 Matlab 中绘制带箭头的坐标系

    如何在 Matlab 中绘制带箭头的坐标系 如何在 Matlab 中绘制带箭头的坐标系 实现原理 演示效果 完整代码 实现原理 使用 matlab 的绘制函数时,默认设置为一个方框形的坐标系, 图1 ...

  7. SVG 箭头线绘制

    SVG并没有提供原生的Arrow标签,这就需要自己的组合了,通过marker标签和path标签可以完美的模仿出箭头线,无论需要多少个箭头线,只需引用同一个marker即可: <svg id=&q ...

  8. css实现带箭头选项卡

    这阵子在做一个web端项目中遇到一个问题,需要实现带箭头的选项卡点击可切换.起初没想太多,直接切一个向上的小箭头图片,外层div设置相同颜色的边框,再用相对定位和绝对定位.这种方法是可行的,但是因为手 ...

  9. iOS重写drawRect方法实现带箭头的View

    创建一个UIView的子类,重写drawRect方法可以实现不规则形状的View,这里提供一个带箭头View的实现代码: ArrowView.h #import <UIKit/UIKit.h&g ...

  10. Leaflet 带箭头轨迹以及沿轨迹带方向的动态marker

    前面写了篇文章,mapboxgl实现带箭头轨迹线,介绍了如何基于mapboxgl实现类似高德地图导航轨迹效果. 下图是我基于leaflet实现的效果. 接下来分享一下在我基于leaflet实现该效果时 ...

随机推荐

  1. Docker安装与教程-Centos7(一)

    复现漏洞时,经常要复现环境,VMware还原太过麻烦,所以学习docker的基本操作也是必要的 Docker三要素-镜像.容器.仓库 操作系统:Centos7 官方教程文档 1.Docker的安装与卸 ...

  2. Windows文件句柄无效

    今天我用FreeFileSync从移动硬盘复制一个名为Con的文件夹到本地硬盘,复制失败. 通过文件夹资源管理器Explorer直接访问文件夹则提示"禁止访问",右键属性切换到安全 ...

  3. C语言从键盘上输入一个梯形的上底a、下底b和高h,输出梯形的面积。

    #include<stdio.h> int main() { double a, b, h, s;//定义变量,上底,下底,高,面积 scanf_s("%lf,%lf,%lf&q ...

  4. 深入了解Rabbit加密技术:原理、实现与应用

    一.引言 在信息时代,数据安全愈发受到重视,加密技术作为保障信息安全的核心手段,得到了广泛的研究与应用.Rabbit加密技术作为一种新型加密方法,具有较高的安全性和便捷性.本文将对Rabbit加密技术 ...

  5. Mysql报:error while loading shared libraries libtinfo.so.5的解决办法

    版权声明:原创作品,谢绝转载!否则将追究法律责任. ----- 作者:kirin #.今天闲来无事,想在Anolis8的系统上装一个MySQL8.0玩.前期在安装和配置的过程中没有什么问题,但是在我想 ...

  6. 基于Raft算法的DLedger-Library分析

    1 背景 在分布式系统应用中,高可用.一致性是经常面临的问题,针对不同的应用场景,我们会选择不同的架构方式,比如master-slave.基于ZooKeeper选主.随着时间的推移,出现了基于Raft ...

  7. 汽车制造业PMC组态应用最佳实践

    01 案例及行业介绍 汽车制造工业是我国国民经济的重要支柱产业,汽车制造工厂一般包含冲压.焊装.涂装.总装四大车间.每辆汽车的生产过程被分解成很多加工任务下发给各个车间进行完成.车辆从冲压车间开始到总 ...

  8. flask中使用pyjwt

    **pyjwt使用教程: ** https://pyjwt.readthedocs.io/en/stable/ 使用案例 import datetime from flask import Flask ...

  9. 创建定义store并使用组合式api、选项式api

    在项目根目录创建store文件夹(此步骤和vuex相同) 在步骤一的store文件夹下根据不同的用途场景创建单独的store文件(等同于vuex中分模块). 定义store基本步骤 步骤 导入defi ...

  10. 华企盾科技:智能AI自动化研判分析服务系统概述

    由中企网安全资子公司北京华企盾科技有限责任公司开发的<智能AI自动化研判分析服务系统>,获得国家版权局颁发的计算机软件著作权登记证书. 智能AI自动化研判分析服务系统是基于人工智能.大数据 ...