分享

使用three.js 绘制三维带箭头线的详细过程

 新用户8177FDBQ 2022-02-10

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

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

部分代码:

DrawPath.js:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

259

260

261

262

263

264

265

266

267

268

269

270

271

272

273

274

275

276

277

278

279

280

281

282

283

/**

 * 绘制路线

 */

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 }

show.js中的部分代码:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

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

效果图:

 

还是有点问题:

 

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

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

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约