用 three.js 绘制三维带箭头线要了我半条命

  • A+
所属分类:Web前端
摘要

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

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

    使用到了 three.js 的 Line2.js 和一个开源库MeshLine.js

部分代码:

DrawPath.js:

用 three.js 绘制三维带箭头线要了我半条命用 three.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', 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 }

View Code

show.js中的部分代码:

用 three.js 绘制三维带箭头线要了我半条命用 three.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);

View Code

效果图:

用 three.js 绘制三维带箭头线要了我半条命

还是有点问题:

用 three.js 绘制三维带箭头线要了我半条命

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

 

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

用 three.js 绘制三维带箭头线要了我半条命