1. 程式人生 > 其它 >用 three.js 繪製三維帶箭頭線 (線內箭頭)

用 three.js 繪製三維帶箭頭線 (線內箭頭)

在LineMaterial.js基礎上修改的ArrowLineMaterial.js程式碼:

/**
 * @author WestLangley / http://github.com/WestLangley
 *
 * parameters = {
 *  color: <hex>,
 *  linewidth: <float>,
 *  dashed: <boolean>,
 *  dashScale: <float>,
 *  dashSize: <float>,
 *  gapSize: <float>,
 *  resolution: <Vector2>, // to be set by renderer
 * }
 
*/ import { ShaderLib, ShaderMaterial, UniformsLib, UniformsUtils, Vector2 } from "../build/three.module.js"; UniformsLib.line = { linewidth: { value: 1 }, resolution: { value: new Vector2(1, 1) }, dashScale: { value: 1 }, dashSize: { value: 1 }, gapSize: { value:
1 } // todo FIX - maybe change to totalSize }; ShaderLib['line'] = { uniforms: UniformsUtils.merge([ UniformsLib.common, UniformsLib.fog, UniformsLib.line ]), vertexShader: ` #include <common> #include <color_pars_vertex> #include
<fog_pars_vertex> #include <logdepthbuf_pars_vertex> #include <clipping_planes_pars_vertex> uniform float linewidth; uniform vec2 resolution; attribute vec3 instanceStart; attribute vec3 instanceEnd; attribute vec3 instanceColorStart; attribute vec3 instanceColorEnd; varying vec2 vUv; varying float lineLength; #ifdef USE_DASH uniform float dashScale; attribute float instanceDistanceStart; attribute float instanceDistanceEnd; varying float vLineDistance; #endif void trimSegment( const in vec4 start, inout vec4 end ) { // trim end segment so it terminates between the camera plane and the near plane // conservative estimate of the near plane float a = projectionMatrix[ 2 ][ 2 ]; // 3nd entry in 3th column float b = projectionMatrix[ 3 ][ 2 ]; // 3nd entry in 4th column float nearEstimate = - 0.5 * b / a; float alpha = ( nearEstimate - start.z ) / ( end.z - start.z ); end.xyz = mix( start.xyz, end.xyz, alpha ); } void main() { #ifdef USE_COLOR vColor.xyz = ( position.y < 0.5 ) ? instanceColorStart : instanceColorEnd; #endif #ifdef USE_DASH vLineDistance = ( position.y < 0.5 ) ? dashScale * instanceDistanceStart : dashScale * instanceDistanceEnd; #endif float aspect = resolution.x / resolution.y; vUv = uv; // camera space vec4 start = modelViewMatrix * vec4( instanceStart, 1.0 ); vec4 end = modelViewMatrix * vec4( instanceEnd, 1.0 ); // special case for perspective projection, and segments that terminate either in, or behind, the camera plane // clearly the gpu firmware has a way of addressing this issue when projecting into ndc space // but we need to perform ndc-space calculations in the shader, so we must address this issue directly // perhaps there is a more elegant solution -- WestLangley bool perspective = ( projectionMatrix[ 2 ][ 3 ] == - 1.0 ); // 4th entry in the 3rd column if ( perspective ) { if ( start.z < 0.0 && end.z >= 0.0 ) { trimSegment( start, end ); } else if ( end.z < 0.0 && start.z >= 0.0 ) { trimSegment( end, start ); } } // clip space vec4 clipStart = projectionMatrix * start; vec4 clipEnd = projectionMatrix * end; // ndc space vec2 ndcStart = clipStart.xy / clipStart.w; vec2 ndcEnd = clipEnd.xy / clipEnd.w; // direction vec2 dir = ndcEnd - ndcStart; // account for clip-space aspect ratio dir.x *= aspect; dir = normalize( dir ); // perpendicular to dir vec2 offset = vec2( dir.y, - dir.x ); // undo aspect ratio adjustment dir.x /= aspect; offset.x /= aspect; // sign flip if ( position.x < 0.0 ) offset *= - 1.0; // endcaps if ( position.y < 0.0 ) { offset += - dir; } else if ( position.y > 1.0 ) { offset += dir; } // adjust for linewidth offset *= linewidth; // adjust for clip-space to screen-space conversion // maybe resolution should be based on viewport ... offset /= resolution.y; // select end vec4 clip = ( position.y < 0.5 ) ? clipStart : clipEnd; // back to clip space offset *= clip.w; clip.xy += offset; gl_Position = clip; vec4 mvPosition = ( position.y < 0.5 ) ? start : end; // this is an approximation //lineLength = distance(ndcStart, ndcEnd); lineLength = distance(ndcStart, ndcEnd) * (1.57 + abs(atan(dir.x / dir.y))) / 2.0; //lineLength = distance(clipStart.xyz, clipEnd.xyz); //lineLength = distance(start.xyz, end.xyz); #include <logdepthbuf_vertex> #include <clipping_planes_vertex> #include <fog_vertex> } `, fragmentShader: ` uniform vec3 diffuse; uniform float opacity; uniform sampler2D map; varying float lineLength; #ifdef USE_DASH uniform float dashSize; uniform float gapSize; #endif varying float vLineDistance; #include <common> #include <color_pars_fragment> #include <fog_pars_fragment> #include <logdepthbuf_pars_fragment> #include <clipping_planes_pars_fragment> varying vec2 vUv; void main() { #include <clipping_planes_fragment> #ifdef USE_DASH if ( vUv.y < - 1.0 || vUv.y > 1.0 ) discard; // discard endcaps if ( mod( vLineDistance, dashSize + gapSize ) > dashSize ) discard; // todo - FIX #endif if ( abs( vUv.y ) > 1.0 ) { float a = vUv.x; float b = ( vUv.y > 0.0 ) ? vUv.y - 1.0 : vUv.y + 1.0; float len2 = a * a + b * b; if ( len2 > 1.0 ) discard; } vec4 diffuseColor = vec4( diffuse, opacity ); #include <logdepthbuf_fragment> #include <color_fragment> vec4 c; if ( abs( vUv.y ) > 1.0 ) { c = vec4(diffuseColor.rgb, diffuseColor.a); } else { vec2 rpt = vec2(0.5, 1.0); rpt.y *= lineLength * 5.0; //rpt.y *= lineLength / 500.0; rpt.y = floor(rpt.y + 0.5); if(rpt.y < 1.0) { rpt.y = 1.0; } if(rpt.y > 5.0) { rpt.y = 5.0; } c = vec4(1.0, 1.0, 1.0, 1.0); c *= texture2D( map, vUv * rpt ); } gl_FragColor = c; //#include <premultiplied_alpha_fragment> //#include <tonemapping_fragment> //#include <encodings_fragment> //#include <fog_fragment> } ` }; var ArrowLineMaterial = function (parameters) { ShaderMaterial.call(this, { type: 'ArrowLineMaterial', uniforms: Object.assign({}, UniformsUtils.clone(ShaderLib['line'].uniforms), { map: { value: null }, }), vertexShader: ShaderLib['line'].vertexShader, fragmentShader: ShaderLib['line'].fragmentShader, clipping: true // required for clipping support }); this.dashed = false; Object.defineProperties(this, { map: { enumerable: true, get: function () { return this.uniforms.map.value; }, set: function (value) { this.uniforms.map.value = value; } }, color: { enumerable: true, get: function () { return this.uniforms.diffuse.value; }, set: function (value) { this.uniforms.diffuse.value = value; } }, linewidth: { enumerable: true, get: function () { return this.uniforms.linewidth.value; }, set: function (value) { this.uniforms.linewidth.value = value; } }, dashScale: { enumerable: true, get: function () { return this.uniforms.dashScale.value; }, set: function (value) { this.uniforms.dashScale.value = value; } }, dashSize: { enumerable: true, get: function () { return this.uniforms.dashSize.value; }, set: function (value) { this.uniforms.dashSize.value = value; } }, gapSize: { enumerable: true, get: function () { return this.uniforms.gapSize.value; }, set: function (value) { this.uniforms.gapSize.value = value; } }, resolution: { enumerable: true, get: function () { return this.uniforms.resolution.value; }, set: function (value) { this.uniforms.resolution.value.copy(value); } } }); this.setValues(parameters); }; ArrowLineMaterial.prototype = Object.create(ShaderMaterial.prototype); ArrowLineMaterial.prototype.constructor = ArrowLineMaterial; ArrowLineMaterial.prototype.isLineMaterial = true; export { ArrowLineMaterial };
View Code

ArrowLineMaterial.js中主要修改部分:

在頂點著色器中定義變數:

varying float lineLength;
View Code

在頂點著色器中計算一下線的長度:

lineLength = distance(ndcStart, ndcEnd) * (1.57 + abs(atan(dir.x / dir.y))) / 2.0;
View Code

在片元著色器中定義變數:

uniform sampler2D map;
varying float lineLength;
View Code

在片元著色器中貼圖:

vec4 c;

if ( abs( vUv.y ) > 1.0 ) {
    c = vec4(diffuseColor.rgb, diffuseColor.a); 
} else {
    vec2 rpt = vec2(0.5, 1.0);
    
    rpt.y *= lineLength * 5.0;
    //rpt.y *= lineLength / 500.0;

    rpt.y = floor(rpt.y + 0.5);
    if(rpt.y < 1.0) { rpt.y = 1.0; }
    if(rpt.y > 5.0) { rpt.y = 5.0; }
    c = vec4(1.0, 1.0, 1.0, 1.0); 
    c *= texture2D( map, vUv * rpt );
}

gl_FragColor = c;
View Code

在片元著色器中註釋掉下面幾行,使線的顏色和canvas中設定的顏色一致:

//#include <premultiplied_alpha_fragment>
//#include <tonemapping_fragment>
//#include <encodings_fragment>
//#include <fog_fragment>
View Code

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.drawArrow2 = function (THREE, renderer, width, height, color) {
        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 = color;
        ctx.fill();

        this.drawArrowBorder(ctx, 450, 0, 500, 0, 550, 50, 500, 100, 450, 100, 500, 50);
        ctx.fillStyle = "#ffffff";
        ctx.fill();

        ctx.restore();

        let canvasTexture = new THREE.CanvasTexture(canvas);
        canvasTexture.magFilter = THREE.NearestFilter;
        canvasTexture.minFilter = THREE.NearestFilter;
        canvasTexture.wrapS = THREE.RepeatWrapping;
        canvasTexture.wrapT = THREE.RepeatWrapping;

        let maxAnisotropy = renderer.capabilities.getMaxAnisotropy();
        canvasTexture.anisotropy = maxAnisotropy;

        return canvasTexture;
    }

    /**
     * 畫線內箭頭
     */
    this.drawArrow3 = function (THREE, renderer, width, height, color) {
        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 = color;
        ctx.fill();

        this.drawArrowBorder(ctx, 0, 350, 0, 400, 50, 450, 100, 400, 100, 350, 50, 400);
        ctx.fillStyle = "#ffffff";
        ctx.fill();

        ctx.restore();

        let canvasTexture = new THREE.CanvasTexture(canvas);
        canvasTexture.magFilter = THREE.NearestFilter;
        canvasTexture.minFilter = THREE.NearestFilter;
        canvasTexture.wrapS = THREE.RepeatWrapping;
        canvasTexture.wrapT = THREE.RepeatWrapping;

        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 }
View Code

DrawPath2.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 { ArrowLineMaterial } from '../js.my/ArrowLineMaterial.js';

import { Utils } from '../js.my/Utils.js';
import { Msg } from '../js.my/Msg.js';

let DrawPath2 = function () {

    let _self = this;

    let _canvasDraw = new CanvasDraw();
    let utils = new Utils();
    let msg = new Msg();

    this._isDrawing = false;
    this._path = [];
    this._lines = [];
    this.color = '#00F300';

    this._depthTest = true;
    this._hide = false;

    let _side = 0;

    let viewerContainerId = '#threeCanvas';
    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);
        }
    }

    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);
                }
            }
        }
    }

    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 () {

    }

    function drawLine(point1, point2, oldLine) {
        let n = Math.round(utils.distance(point1.x, point1.y, point1.z, point2.x, point2.y, point2.z) / 500);
        if (n < 1) n = 1;
        for (let i = 0; i < n; i++) {
            let p1 = {};
            p1.x = point1.x + (point2.x - point1.x) / n * i;
            p1.y = point1.y + (point2.y - point1.y) / n * i;
            p1.z = point1.z + (point2.z - point1.z) / n * i;

            let p2 = {};
            p2.x = point1.x + (point2.x - point1.x) / n * (i + 1);
            p2.y = point1.y + (point2.y - point1.y) / n * (i + 1);
            p2.z = point1.z + (point2.z - point1.z) / n * (i + 1);

            drawLine2(p1, p2, oldLine);
        }
    }

    function drawLine2(point1, point2, oldLine) {
        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 canvasTexture = _canvasDraw.drawArrow3(THREE, renderer, 100, 800, _self.color); //箭頭

        let matLine = new ArrowLineMaterial({
            map: canvasTexture,
            color: new THREE.Color(0xffffff),
            linewidth: 0.005, // 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())
        });

        if (!oldLine) {
            let line = new Line2(geometry, matLine);
            line.computeLineDistances();
            line.scale.set(50, 50, 50);

            scene.add(line);
            _self._lines.push(line);
        } else {
            oldLine.geometry = geometry;
            oldLine.material = matLine;
            oldLine.computeLineDistances();
        }

    }

    this.setDepthTest = function (bl) {
        if (bl) {
            _self._depthTest = true;
            this._lines.map(line => {
                line.material.depthTest = true;
                line.material.side = 0;
            });
        } else {
            _self._depthTest = false;
            this._lines.map(line => {
                line.material.depthTest = false;
                line.material.side = THREE.DoubleSide;
            });
        }
    }

    this.getPath = function () {
        return this._path;
    }

    this.hide = function () {
        this._lines.map(line => scene.remove(line));
        this._hide = true;
    }

    this.show = function () {
        this._lines.map(line => scene.add(line));
        this._hide = false;
    }

    this.isShow = function () {
        return !this._hide;
    }

    this.create = function (path, color) {
        _self.color = color;
        _self._path = path;

        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);
            }
        }
    }

    this.getDepthTest = function () {
        return _self._depthTest;
    }

    this.undo = function () {
        scene.remove(this._lines[this._lines.length - 1]);
        _self._path.splice(this._path.length - 1, 1);
        _self._lines.splice(this._lines.length - 1, 1);
    }

}

DrawPath2.prototype.constructor = DrawPath2;

export { DrawPath2 }
View Code

效果圖:

缺陷:

2.5D視角觀察,看著還行,但是把相機拉近觀察,箭頭就會變形。湊合著用。

箭頭貼圖變形或者箭頭顯示不全,原因我猜可能是因為在場景中,線的遠離相機的一端,在螢幕座標系中比較細,線的靠近相機的一端,在螢幕座標系中比較粗,但為了使線的粗細一樣,靠近相機的一端被裁剪了,所以箭頭可能會顯示不全。

不管是MeshLine還是three.js的Line2,這個頻寬度的線,和三維場景中的三維模型是有區別的,無論場景拉近還是拉遠,線的寬度不變,而三維模型場景拉遠變小,拉近變大。

Drawing lines is hard, drawing arrow lines is even harder!

參考:

https://www.cnblogs.com/dojo-lzz/p/9219290.html

https://blog.csdn.net/Amesteur/article/details/95964526