使用three.js 繪製三維帶箭頭線的詳細過程
阿新 • • 發佈:2021-11-02
需求:這個需求是個剛需啊!在一個地鐵場景裡展示逃生路線,這個路線肯定是要有指示箭頭的,為了畫這個箭頭,我花了不少於十幾個小時,總算做出來了,但始終有點問題。我對這個箭頭的要求是,無論場景拉近還是拉遠,這個箭頭不能太大,也不能太小看不清,形狀不能變化,否則就不像箭頭了。
使用到了 three. 的 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 = []; 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("請點選地面畫線"); } this.stop = function () { if (this._isDrawing) { this._isDrawing = false; viewerContainer.removeEventListener('click',mouseup); } msg.show("停止畫線"); } function mousedown(params) { this._mousedownPosition = { x: camera.position.x,z: camera.position.z } } function mouseup(params) { this._mouseupPosition = { x: camera.position.x,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 = standardVectlCluxccTor.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,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,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); scene.add(mesh); _self._arrows.push(mesh); } function refreshArrow(point1,arrow) { let arrowLine = _self.createArrowLine(point1,color: newTHREE.Color(0x00f300),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.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);
效果圖:
還是有點問題:
雖然這個效果圖中,場景拉近,箭頭有點大,但是最大大小還是做了控制的,就是這個形狀有點問題,可能是視角的問題。
我期望的效果應該是這樣的,就是無論從什麼角度看,箭頭不要變形:
到此這篇關於用 three.js 繪製三維帶箭頭線要了我半條命的文章就介紹到這了,更多相關three.js 三維帶箭頭線內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!