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

使用到了 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. Java Junit单元测试(入门必看篇)

    Hi i,m JinXiang 前言 本篇文章主要介绍单元测试工具Junit使用以及部分理论知识 欢迎点赞  收藏 留言评论 私信必回哟 博主收将持续更新学习记录获,友友们有任何问题可以在评论区留言 ...

  2. 【Android Studio】 SQLite 数据库 增删改查

    今日目标:参照标题 实现效果: 实现过程: 1.创建实现页面 页面设计思路: 最外层垂直线性布局.内部使用三个线性布局.后两个用的水平排版,用来显示数值. 实例化四个Button,两个EditText ...

  3. 如何检测Windows服务停止后自动启动?自动运行.bat批处理文件?

    作者:西瓜程序猿 主页传送门:https://www.cnblogs.com/kimiliucn 前言 想要确保你的Windows服务即使在崩溃后也能自动重启吗?这篇文章教你如何用一个小巧的批处理脚本 ...

  4. 存储器 Memory

    缩写 全称 翻译 用途 RAM Random Access Memory 随机存储器 SRAM Static RAM 静态RAM MCU的片上RAMCPU的L1.L2 cache DRAM Dynam ...

  5. Vue2.0 学习 第二组 语法模板

    本笔记主要参考菜鸟教程和官方文档编写. 1.文本绑定 一般在dom中用{{}}标时,并且在vue构造体内的data中定义文本内容 <div id="app">    & ...

  6. Http请求超好用的工具类

    话题不多说,直接开整 1.先导入依赖 <dependency> <groupId>io.github.admin4j</groupId> <artifactI ...

  7. ceph集群搭建详细教程(ceph-deploy)

    ceph-deploy比较适合生产环境,不是用cephadm搭建.相对麻烦一些,但是并不难,细节把握好就行,只是命令多一些而已. 实验环境 服务器主机 public网段IP(对外服务) cluster ...

  8. Oracle-Rman备份全解析

    RMAN备份数据库物理文件到备份集(backupset)中.在创建备份集时,仅备份已经使用的数据库(不备份空闲的数据块),而且还可以采用压缩功能. RMAN恢复时指当数据库出现介质失败时,使用RMAN ...

  9. 7.elasticsearch重建索引

    什么时候需要重建索引 索引的mappings发生变更 索引的setting发生变更 集群内,集群间,需要做数据迁移 update by query 在现有索引重建 比如需要给一个text新增一个子字段 ...

  10. mysql 数据库 定时 备份到阿里云盘

    仓库地址: gitee:db_backup_script: mysql 数据库 定时/实时 备份数据库到阿里云盘,备份成功后消息可通知到钉钉群.企业微信群.wxpusher (gitee.com gi ...