使用three.js 繪制三維帶箭頭線的詳細(xì)過程
需求:這個(gè)需求是個(gè)剛需啊!在一個(gè)地鐵場景里展示逃生路線,這個(gè)路線肯定是要有指示箭頭的,為了畫這個(gè)箭頭,我花了不少于十幾個(gè)小時(shí),總算做出來了,但始終有點(diǎn)問題。我對這個(gè)箭頭的要求是,無論場景拉近還是拉遠(yuǎn),這個(gè)箭頭不能太大,也不能太小看不清,形狀不能變化,否則就不像箭頭了。
使用到了 three.js 的 Line2.js 和一個(gè)開源庫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("請點(diǎn)擊地面畫線");
}
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);
效果圖:

還是有點(diǎn)問題:

雖然這個(gè)效果圖中,場景拉近,箭頭有點(diǎn)大,但是最大大小還是做了控制的,就是這個(gè)形狀有點(diǎn)問題,可能是視角的問題。
我期望的效果應(yīng)該是這樣的,就是無論從什么角度看,箭頭不要變形:

到此這篇關(guān)于用 three.js 繪制三維帶箭頭線要了我半條命的文章就介紹到這了,更多相關(guān)three.js 三維帶箭頭線內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JavaScript檢測瀏覽器cookie是否已經(jīng)啟動(dòng)的方法
這篇文章主要介紹了JavaScript檢測瀏覽器cookie是否已經(jīng)啟動(dòng)的方法,實(shí)例分析了javascript操作cookie的技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-02-02
使用JavaScript實(shí)現(xiàn)一個(gè)物理模擬
最近掌門人在寫3D游戲,對于其中的物理效果很感興趣,今天我將使用純JavaScript來實(shí)現(xiàn)一個(gè)簡易的物理模擬,其中包括碰撞檢測與響應(yīng)、摩擦力與空氣阻力、以及物體的破壞效果,文中通過代碼示例講解的非常詳細(xì),需要的朋友可以參考下2024-01-01
自適應(yīng)高度框架 ----屬個(gè)人收藏內(nèi)容
自適應(yīng)高度框架 ----屬個(gè)人收藏內(nèi)容...2007-01-01
javaScript讓文本框內(nèi)的最后一個(gè)文字的后面獲得焦點(diǎn)實(shí)現(xiàn)代碼
讓文本框內(nèi)的最后一個(gè)文字的后面獲得焦點(diǎn),在應(yīng)用中很常見,接下來提供解決方案,按興趣的朋友可以了解下2013-01-01
JS對象與json字符串相互轉(zhuǎn)換實(shí)現(xiàn)方法示例
這篇文章主要介紹了JS對象與json字符串相互轉(zhuǎn)換實(shí)現(xiàn)方法,結(jié)合實(shí)例形式分析了js對象與json字符串相互轉(zhuǎn)換的相關(guān)操作技巧與注意事項(xiàng),需要的朋友可以參考下2018-06-06
uniapp開發(fā)h5項(xiàng)目引入第三方j(luò)s(sdk)兩種方法
這篇文章主要給大家介紹了關(guān)于uniapp開發(fā)h5項(xiàng)目引入第三方j(luò)s(sdk)的兩種方法,在Uniapp中引入JS文件是一項(xiàng)常見的操作,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-02-02

