├── .eslintignore
├── .prettierrc.json
├── babel.config.json
├── .editorconfig
├── .eslintrc
├── examples
├── index.html
├── css
│ └── main.css
├── simple.html
├── modifiers.html
└── all.html
├── .gitignore
├── LICENSE
├── package.json
├── README.md
└── src
└── index.js
/.eslintignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 |
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true
3 | }
4 |
--------------------------------------------------------------------------------
/babel.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "@babel/preset-env",
5 | {
6 | "modules": false
7 | }
8 | ]
9 | ]
10 | }
11 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | charset = utf-8
9 | trim_trailing_whitespace = true
10 | insert_final_newline = true
11 |
12 | [*.md]
13 | trim_trailing_whitespace = false
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "parser": "@babel/eslint-parser",
4 | "extends": ["eslint:recommended", "prettier"],
5 | "env": {
6 | "browser": true,
7 | "es6": true
8 | },
9 | "parserOptions": {
10 | "ecmaVersion": 2021,
11 | "sourceType": "module"
12 | },
13 | "rules": {
14 | "no-unused-vars": [1],
15 | "comma-dangle": ["error", "only-multiline"],
16 | "semi": [2, "always"],
17 | "strict": [2, "never"],
18 | "indent": [2, 2],
19 | "quotes": [2, "single"],
20 | "linebreak-style": [2, "unix"],
21 | "no-reserved-keys": 0,
22 | "no-wrap-func": 0,
23 | "no-console": 0,
24 | "spaced-line-comment": 0,
25 | "no-multiple-empty-lines": [
26 | 2,
27 | {
28 | "max": 3
29 | }
30 | ]
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/examples/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | THREE.BlurredLine Examples
5 |
6 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
THREE.BlurredLine Examples
17 |
18 |
19 |
24 |
25 |
26 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # OSX specific
4 | .DS_Store
5 | .AppleDouble
6 | .LSOverride
7 |
8 | # Icon must end with two \r
9 | Icon
10 |
11 |
12 | # Thumbnails
13 | ._*
14 |
15 | # Files that might appear in the root of a volume
16 | .DocumentRevisions-V100
17 | .fseventsd
18 | .Spotlight-V100
19 | .TemporaryItems
20 | .Trashes
21 | .VolumeIcon.icns
22 |
23 | # Directories potentially created on remote AFP share
24 | .AppleDB
25 | .AppleDesktop
26 | Network Trash Folder
27 | Temporary Items
28 | .apdisk
29 |
30 |
31 | # Node related
32 | Logs
33 | logs
34 | *.log
35 | npm-debug.log*
36 |
37 | # Runtime data
38 | pids
39 | *.pid
40 | *.seed
41 |
42 | # Directory for instrumented libs generated by jscoverage/JSCover
43 | lib-cov
44 |
45 | # Coverage directory used by tools like istanbul
46 | coverage
47 |
48 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
49 | .grunt
50 |
51 | # node-waf configuration
52 | .lock-wscript
53 |
54 | # Dependency directory
55 | node_modules
56 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Markus Lerner
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "three.blurredline",
3 | "version": "1.3.1",
4 | "description": "Drawing smooth blurred lines in THREE.js",
5 | "type": "module",
6 | "module": "./build/three.blurredline.js",
7 | "scripts": {
8 | "test": "echo \"Error: no test specified\" && exit 1",
9 | "build": "esbuild src/index.js --bundle --minify --format=esm --sourcemap --external:three --outfile=build/three.blurredline.js",
10 | "lint": "eslint src/** --ext .js",
11 | "examples": "ws"
12 | },
13 | "repository": {
14 | "type": "git",
15 | "url": "git+https://github.com/markuslerner/THREE.BlurredLine.git"
16 | },
17 | "keywords": [
18 | "lines",
19 | "threejs",
20 | "smooth",
21 | "blur"
22 | ],
23 | "files": [
24 | "build/three.blurredline.js",
25 | "build/three.blurredline.js.map",
26 | "examples",
27 | "LICENSE",
28 | "package.json",
29 | "README.md",
30 | "src"
31 | ],
32 | "author": "Markus Lerner (https://www.markuslerner.com)",
33 | "license": "MIT",
34 | "bugs": {
35 | "url": "https://github.com/markuslerner/THREE.BlurredLine/issues"
36 | },
37 | "homepage": "https://github.com/markuslerner/THREE.BlurredLine#readme",
38 | "devDependencies": {
39 | "@babel/core": "^7.20.12",
40 | "@babel/eslint-parser": "^7.19.1",
41 | "@babel/preset-env": "^7.20.2",
42 | "esbuild": "^0.14.23",
43 | "eslint": "^8.13.0",
44 | "eslint-config-prettier": "^8.5.0",
45 | "local-web-server": "^5.4.0",
46 | "prettier": "^2.6.2"
47 | },
48 | "peerDependencies": {
49 | "three": ">= 0.137.0"
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/examples/css/main.css:
--------------------------------------------------------------------------------
1 | * {
2 | box-sizing: border-box;
3 | margin: 0;
4 | padding: 0;
5 | }
6 |
7 | body {
8 | overflow: hidden;
9 | font-family: Helvetica, Arial, sans-serif;
10 | width: 100%;
11 | height: 100%;
12 | background-color: #dedede;
13 | color: #202020;
14 | padding: 20px;
15 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
16 | }
17 |
18 | canvas {
19 | outline: none;
20 | }
21 |
22 | h1 {
23 | font-weight: 300;
24 | font-size: 24px;
25 | margin-bottom: 0.5em;
26 | }
27 |
28 | h1 a {
29 | text-decoration: none;
30 | }
31 |
32 | a {
33 | color: #333;
34 | transition: color 0.2s ease;
35 | }
36 |
37 | a:visited {
38 | color: #555;
39 | }
40 |
41 | a:hover {
42 | color: #111;
43 | }
44 |
45 | a:focus-visible {
46 | color: #111;
47 | }
48 |
49 | ul {
50 | margin: 30px 0px;
51 | padding: 0px;
52 | line-height: 1.5em;
53 | }
54 |
55 | ul li {
56 | list-style-type: none;
57 | }
58 |
59 | #container {
60 | position: absolute;
61 | left: 0;
62 | top: 0;
63 | right: 0;
64 | bottom: 0;
65 | width: 100%;
66 | height: 100%;
67 | border: 1px solid transparent;
68 | background-color: #fff;
69 | background-image: linear-gradient(#ccc 1px, transparent 1px),
70 | linear-gradient(90deg, #ccc 1px, transparent 1px),
71 | linear-gradient(rgba(0, 0, 0, 0.1) 1px, transparent 1px),
72 | linear-gradient(90deg, rgba(0, 0, 0, 0.1) 1px, transparent 1px);
73 | background-size: 100px 100px, 100px 100px, 10px 10px, 10px 10px;
74 | background-position: calc(50vw - 0px) calc(50vh - 1px),
75 | calc(50vw - 0px) calc(50vh - 1px), calc(50vw - 0px) calc(50vh - 1px),
76 | calc(50vw - 0px) calc(50vh - 1px);
77 | z-index: 0;
78 | }
79 |
80 | #title {
81 | position: absolute;
82 | left: 25px;
83 | top: 20px;
84 | z-index: 1;
85 | }
86 |
87 | #title p {
88 | max-width: 350px;
89 | }
90 |
91 | #title .log {
92 | font-size: 12px;
93 | }
94 |
95 | #info {
96 | z-index: 1;
97 | position: absolute;
98 | left: 25px;
99 | bottom: 40px;
100 | z-index: 1;
101 | }
102 |
103 | #progress-bar {
104 | position: absolute;
105 | left: 0px;
106 | top: 50vh;
107 | width: 0px;
108 | height: 5px;
109 | background: #666666;
110 | z-index: 1;
111 | transition: all 0.5s ease;
112 | }
113 |
--------------------------------------------------------------------------------
/examples/simple.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | THREE.BlurredLine Simple Example
5 |
6 |
10 |
11 |
12 |
13 |
17 |
18 |
26 |
27 |
28 |
29 |
30 |
31 |
35 |
36 |
43 |
44 |
116 |
117 |
118 |
--------------------------------------------------------------------------------
/examples/modifiers.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | THREE.BlurredLine Simple Example
5 |
6 |
10 |
11 |
12 |
13 |
17 |
18 |
26 |
27 |
28 |
29 |
30 |
31 |
35 |
36 |
43 |
44 |
140 |
141 |
142 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # THREE.BlurredLine
2 |
3 | [](https://www.npmjs.com/package/three.blurredline)
4 |
5 | Draw lines of varying width, blur, color and opacity in [THREE.js](https://github.com/mrdoob/three.js/)
6 |
7 | This libary draws lines as a mesh and internally creates a BufferGeometry.
8 |
9 | It is useful not only for drawing wide soft lines, but also extremely thin lines neatly.
10 |
11 | ## Examples
12 |
13 | - [All features](https://dev.markuslerner.com/three.blurredline/examples/all.html): Demo of all the features
14 | - [Simple](https://dev.markuslerner.com/three.blurredline/examples/simple.html): Simple Example
15 | - [Modifiers](https://dev.markuslerner.com/three.blurredline/examples/modifiers.html): Using modifier functions to draw varying line widths, blur widths and colors
16 |
17 | ## Usage
18 |
19 | - Include script
20 | - Create a [Curve](https://threejs.org/docs/#api/en/extras/core/Curve)
21 | - Create a BlurredLineMaterial
22 | - Create a BlurredLine supplying curve and material
23 |
24 | ### Including the script
25 |
26 | Include script after THREE is included:
27 |
28 | ```js
29 |
30 | ```
31 |
32 | or include directly from unpkg.com:
33 |
34 | ```js
35 |
36 | ```
37 |
38 | or use npm to install it:
39 |
40 | ```console
41 | npm i three.blurredline
42 | ```
43 |
44 | or use yarn to install it:
45 |
46 | ```console
47 | yarn add three.blurredline
48 | ```
49 |
50 | and include it in your code:
51 |
52 | ```js
53 | import * as THREE from 'three';
54 | import { BlurredLine, BlurredLineMaterial } from 'three.blurredline';
55 | ```
56 |
57 | #### Create a Curve
58 |
59 | First create a [Curve](https://threejs.org/docs/#api/en/extras/core/Curve) e.g. using THREE.CubicBezierCurve3, THREE.EllipseCurve, THREE.LineCurve3, THREE.Path or THREE.SplineCurve.
60 |
61 | ```js
62 | var curve = new THREE.LineCurve3(
63 | new THREE.Vector3(0, 0, 0),
64 | new THREE.Vector3(100, 0, 0)
65 | );
66 | ```
67 |
68 | #### Create a BlurredLineMaterial
69 |
70 | ```js
71 | var material = new BlurredLineMaterial({
72 | color: new THREE.Color('#FF0000'),
73 | opacity: 1.0,
74 | });
75 | ```
76 |
77 | - `color` – `THREE.Color`
78 | - `opacity` – alpha value from 0 to 1
79 |
80 | #### Create a BlurredLine
81 |
82 | Use Curve and BlurredLineMaterial to create a BlurredLine mesh, call `updateGeometry()` and add it to the scene. The third parameter specifies the resolution:
83 |
84 | ```js
85 | var line = new BlurredLine(curve, material, 50);
86 | line.lineWidth = 2.0;
87 | line.blurWidth = 10.0;
88 | line.blur = true;
89 | line.updateGeometry();
90 | scene.add(line);
91 | ```
92 |
93 | `updateGeometry()` needs to be called again whenever any parameter affecting the geometry changes (angleBisection, blur, blurWidth, blurWidthModifier, curve, lineWidth, lineWidthModifier, upVector).
94 |
95 | ## API
96 |
97 | ### BlurredLine class
98 |
99 | Extends [THREE.Mesh](https://threejs.org/docs/#api/en/objects/Mesh).
100 |
101 | **Constructor:**
102 |
103 | BlurredLine(curve : THREE.Curve, material: BlurredLineMaterial, resolution : number)
104 |
105 | **Variables:**
106 |
107 | `angleBisection`: boolean – use angle bisection to calculate the side vectors (better for 2D lines)
108 |
109 | `blur`: boolean – whether to use blurWidth or draw hard lines instead (uses less triangles when set to false)
110 |
111 | `blurWidth`: number – blur width of the line
112 |
113 | `blurWidthModifier`: function – modifier function for the blur width of the line. Has to return a number. For example cubic easing in: `(p) => { return p * p; }`
114 |
115 | `closed`: boolean – whether the line is closed (e.g. used for ellipses)
116 |
117 | `color`: THREE.Color – defaults to white and is multiplied with material color while drawing.
118 |
119 | `colorModifier`: function – modifier function for the line color. Has to return a THREE.Color. colorModifier is multiplied with color and material color while drawing. For example for a transition between two colors: `(p) => { return new THREE.Color(0x000000).lerp(new THREE.Color(0xff0000), p); }`
120 |
121 | `curve`: THREE.Curve – base curve
122 |
123 | `lineWidth`: number – thickness of the line
124 |
125 | `lineWidthModifier`: function – modifier function for the width of the line. Has to return a number. For example cubic easing in: `(p) => { return p * p; }`
126 |
127 | `opacity`: number – opacity of the line, is multiplied with material opacity while drawing.
128 |
129 | `opacityModifier`: function – modifier function for the opacity of the line. Has to return a number. opacityModifier is multiplied with color and material color while drawing. For example cubic easing in: `(p) => { return p * p; }`
130 |
131 | `upVector`: THREE.Vector3 – upvector, e.g. vector facing the camera, defaults to (0.0, 0.0, 1.0).
132 |
133 | **Methods:**
134 |
135 | `updateColors()` – needs to be called again whenever any parameter affecting the color changes (color, colorModifier, opacity, opacityModifier). Material parameter changes are updated automatically.
136 |
137 | `updateGeometry()` – needs to be called again whenever any parameter affecting the geometry changes (angleBisection, blur, blurWidth, blurWidthModifier, curve, lineWidth, lineWidthModifier, upVector). Internally calls updateColors() as well. Material parameter changes are updated automatically.
138 |
139 | ### BlurredLineMaterial class
140 |
141 | Extends [THREE.RawShaderMaterial](https://threejs.org/docs/#api/en/materials/RawShaderMaterial).
142 |
143 | **Constructor:**
144 |
145 | BlurredLineMaterial(parameters : Object)
146 | parameters – (optional) an object with one or more properties defining the material's appearance. Any property of the material (including any property inherited from Material and ShaderMaterial) can be passed in here.
147 |
148 | **Properties:**
149 |
150 | Also see the base [THREE.RawShaderMaterial](https://threejs.org/docs/#api/en/materials/RawShaderMaterial) class for common properties.
151 |
152 | `color` (THREE.Color) – defaults to white and is multiplied with material color while drawing.
153 |
154 | `opacity` (number) – opacity of the line, is multiplied with material opacity while drawing.
155 |
156 | **Methods:**
157 |
158 | See the base [THREE.RawShaderMaterial](https://threejs.org/docs/#api/en/materials/RawShaderMaterial) class for common methods.
159 |
160 | ### License
161 |
162 | MIT licensed
163 |
164 | Copyright (C) 2021 Markus Lerner, http://www.markuslerner.com
165 |
--------------------------------------------------------------------------------
/examples/all.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | THREE.BlurredLine Example
5 |
6 |
10 |
11 |
12 |
13 |
19 |
20 |
28 |
29 |
30 |
31 |
32 |
33 |
37 |
38 |
46 |
47 |
375 |
376 |
377 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import {
2 | BufferGeometry,
3 | BufferAttribute,
4 | Color,
5 | DoubleSide,
6 | Mesh,
7 | RawShaderMaterial,
8 | ShaderMaterial,
9 | Vector2,
10 | Vector3,
11 | } from 'three';
12 |
13 | const LEFT_LINE = 0;
14 | const RIGHT_LINE = 1;
15 | const LEFT_SMOOTH_LINE = 2;
16 | const RIGHT_SMOOTH_LINE = 3;
17 |
18 | export class BlurredLine extends Mesh {
19 | constructor(curve, material, resolution = 1) {
20 | super(undefined, material);
21 |
22 | this.type = 'BlurredLine';
23 |
24 | this._resolution = resolution;
25 |
26 | this.lineWidth = 1.0;
27 | this.blurWidth = 1.0;
28 | this.blur = true;
29 | this.color = new Color();
30 | this.opacity = 1.0;
31 |
32 | this.upVector = new Vector3(0.0, 0.0, 1.0);
33 |
34 | this.closed = false;
35 |
36 | this.angleBisection = false; // true: good for 2d
37 |
38 | this.lineShapeVertices = [[]];
39 |
40 | // this.calculateNormals = true;
41 |
42 | this.lineVertices = [];
43 | this.curve = curve; // curve to read vertices from
44 |
45 | for (let i = 0; i < this._resolution + 1; i++) {
46 | this.lineVertices[i] = new Vector3();
47 | }
48 |
49 | this.lineShapeVertices = [];
50 | for (let i = 0; i < this._resolution + 1; i++) {
51 | var vertices = [];
52 | vertices[LEFT_LINE] = new Vector3();
53 | vertices[RIGHT_LINE] = new Vector3();
54 | vertices[LEFT_SMOOTH_LINE] = new Vector3();
55 | vertices[RIGHT_SMOOTH_LINE] = new Vector3();
56 | this.lineShapeVertices[i] = vertices;
57 | }
58 |
59 | this.createGeometry();
60 | }
61 |
62 | createGeometry() {
63 | var trianglesCount =
64 | (this.lineShapeVertices.length - 1) * (this.blur ? 6 : 2);
65 | this.geometry = new BufferGeometry();
66 | this.positions = new Float32Array(trianglesCount * 3 * 3);
67 | // this.normals = new Float32Array(trianglesCount * 3 * 3);
68 | this.vertexColors = new Float32Array(trianglesCount * 3 * 4);
69 |
70 | var positionAttribute = new BufferAttribute(this.positions, 3);
71 | // var normalAttribute = new BufferAttribute(this.normals, 3);
72 | var colorAttribute = new BufferAttribute(this.vertexColors, 4);
73 |
74 | this.geometry.setAttribute('position', positionAttribute);
75 | // this.geometry.setAttribute('normal', normalAttribute);
76 | this.geometry.setAttribute('color', colorAttribute);
77 | this.geometry.computeBoundingSphere();
78 | }
79 |
80 | updateGeometry(filled = false) {
81 | // console.log('updateGeometry()');
82 |
83 | if (this.curve !== null) {
84 | // console.log( this.curve.getPoints(this._resolution) );
85 | this.lineVertices = this.curve.getPoints(this._resolution);
86 | if (
87 | this.lineVertices.length > 0 &&
88 | this.lineVertices[0] instanceof Vector2
89 | ) {
90 | for (let i = 0; i < this.lineShapeVertices.length; i++) {
91 | var v = this.lineVertices[i];
92 | this.lineVertices[i] = new Vector3(v.x, v.y, 0.0);
93 | }
94 | }
95 | }
96 |
97 | if (this.lineVertices !== null) {
98 | this.geometry.attributes.position.needsUpdate = true;
99 | // this.geometry.attributes.normal.needsUpdate = true;
100 |
101 | this.updateLineShapeVertices();
102 |
103 | var lineShapeVertices = this.lineShapeVertices;
104 |
105 | for (let i = 0; i < this.lineShapeVertices.length - 1; i++) {
106 | var index = i * 3 * 3 * (this.blur ? 6 : 2);
107 |
108 | if (filled) {
109 | // lineAtoms[i].setVertices(this.lineShapeVertices[i][LEFT_LINE],
110 | // lineVertices[i],
111 | // lineVertices[i],
112 | // lineVertices[i + 1],
113 | // lineVertices[i + 1],
114 | // lineShapeVertices[i + 1][LEFT_LINE],
115 | // lineShapeVertices[i + 1][LEFT_SMOOTH_LINE],
116 | // lineShapeVertices[i][LEFT_SMOOTH_LINE]);
117 | } else {
118 | // 6 triangles:
119 |
120 | // line
121 | updatePosition(
122 | this.positions,
123 | index,
124 | lineShapeVertices[i][LEFT_LINE]
125 | );
126 | updatePosition(
127 | this.positions,
128 | index + 3,
129 | lineShapeVertices[i + 1][LEFT_LINE]
130 | );
131 | updatePosition(
132 | this.positions,
133 | index + 6,
134 | lineShapeVertices[i][RIGHT_LINE]
135 | );
136 |
137 | updatePosition(
138 | this.positions,
139 | index + 9,
140 | lineShapeVertices[i][RIGHT_LINE]
141 | );
142 | updatePosition(
143 | this.positions,
144 | index + 12,
145 | lineShapeVertices[i + 1][LEFT_LINE]
146 | );
147 | updatePosition(
148 | this.positions,
149 | index + 15,
150 | lineShapeVertices[i + 1][RIGHT_LINE]
151 | );
152 |
153 | if (this.blur) {
154 | // left blur
155 | updatePosition(
156 | this.positions,
157 | index + 18,
158 | lineShapeVertices[i][LEFT_LINE]
159 | );
160 | updatePosition(
161 | this.positions,
162 | index + 21,
163 | lineShapeVertices[i][LEFT_SMOOTH_LINE]
164 | );
165 | updatePosition(
166 | this.positions,
167 | index + 24,
168 | lineShapeVertices[i + 1][LEFT_SMOOTH_LINE]
169 | );
170 |
171 | updatePosition(
172 | this.positions,
173 | index + 27,
174 | lineShapeVertices[i][LEFT_LINE]
175 | );
176 | updatePosition(
177 | this.positions,
178 | index + 30,
179 | lineShapeVertices[i + 1][LEFT_SMOOTH_LINE]
180 | );
181 | updatePosition(
182 | this.positions,
183 | index + 33,
184 | lineShapeVertices[i + 1][LEFT_LINE]
185 | );
186 |
187 | // right blur
188 | updatePosition(
189 | this.positions,
190 | index + 36,
191 | lineShapeVertices[i][RIGHT_LINE]
192 | );
193 | updatePosition(
194 | this.positions,
195 | index + 39,
196 | lineShapeVertices[i + 1][RIGHT_LINE]
197 | );
198 | updatePosition(
199 | this.positions,
200 | index + 42,
201 | lineShapeVertices[i][RIGHT_SMOOTH_LINE]
202 | );
203 |
204 | updatePosition(
205 | this.positions,
206 | index + 45,
207 | lineShapeVertices[i][RIGHT_SMOOTH_LINE]
208 | );
209 | updatePosition(
210 | this.positions,
211 | index + 48,
212 | lineShapeVertices[i + 1][RIGHT_LINE]
213 | );
214 | updatePosition(
215 | this.positions,
216 | index + 51,
217 | lineShapeVertices[i + 1][RIGHT_SMOOTH_LINE]
218 | );
219 | }
220 |
221 | // flat face normals
222 | // if(this.calculateNormals) {
223 | // for(let c = 0; c < 6 * 9; c += 9) {
224 | // var pA = new Vector3(this.positions[index + c + 0], this.positions[index + c + 1], this.positions[index + c + 2]);
225 | // var pB = new Vector3(this.positions[index + c + 3], this.positions[index + c + 4], this.positions[index + c + 5]);
226 | // var pC = new Vector3(this.positions[index + c + 6], this.positions[index + c + 7], this.positions[index + c + 8]);
227 | // var cb = new Vector3().subVectors(pC, pB);
228 | // var ab = new Vector3().subVectors(pA, pB);
229 | // cb.cross(ab);
230 | // cb.normalize();
231 | // updatePosition(this.normals, index + c, cb);
232 | // if(this.blur) {
233 | // updatePosition(this.normals, index + c + 3, cb);
234 | // updatePosition(this.normals, index + c + 6, cb);
235 | // }
236 | // }
237 | // } else {
238 | // for(let c = 0; c < 6 * 9; c += 9) {
239 | // updatePosition(this.normals, index + c, this.upVector);
240 | // if(this.blur) {
241 | // updatePosition(this.normals, index + c + 3, this.upVector);
242 | // updatePosition(this.normals, index + c + 6, this.upVector);
243 | // }
244 | // }
245 | // }
246 | }
247 | }
248 |
249 | this.updateColors();
250 |
251 | // this.geometry.computeBoundingSphere();
252 | }
253 | }
254 |
255 | updateColors() {
256 | this.geometry.attributes.color.needsUpdate = true;
257 |
258 | for (let i = 0; i < this.lineShapeVertices.length - 1; i++) {
259 | var index = i * 3 * 4 * (this.blur ? 6 : 2);
260 |
261 | const p = i / (this._resolution - 1);
262 | var c = this._getColor(p);
263 | var o = this._getOpacity(p);
264 |
265 | // line
266 | updateColor(this.vertexColors, index, c, o);
267 | updateColor(this.vertexColors, index + 4, c, o);
268 | updateColor(this.vertexColors, index + 8, c, o);
269 |
270 | updateColor(this.vertexColors, index + 12, c, o);
271 | updateColor(this.vertexColors, index + 16, c, o);
272 | updateColor(this.vertexColors, index + 20, c, o);
273 |
274 | if (this.blur) {
275 | // left blur
276 | updateColor(this.vertexColors, index + 24, c, o);
277 | updateColor(this.vertexColors, index + 28, c, 0);
278 | updateColor(this.vertexColors, index + 32, c, 0);
279 |
280 | updateColor(this.vertexColors, index + 36, c, o);
281 | updateColor(this.vertexColors, index + 40, c, 0);
282 | updateColor(this.vertexColors, index + 44, c, o);
283 |
284 | // right blur
285 | updateColor(this.vertexColors, index + 48, c, o);
286 | updateColor(this.vertexColors, index + 52, c, o);
287 | updateColor(this.vertexColors, index + 56, c, 0);
288 |
289 | updateColor(this.vertexColors, index + 60, c, 0);
290 | updateColor(this.vertexColors, index + 64, c, o);
291 | updateColor(this.vertexColors, index + 68, c, 0);
292 | }
293 | }
294 | }
295 |
296 | getLength() {
297 | let l = 0.0;
298 | for (let i = 0; i < this.lineVertices.length - 1; i++) {
299 | l += this.lineVertices[i].distanceTo(this.lineVertices[i + 1]);
300 | }
301 | return l;
302 | }
303 |
304 | // scale widths and alpha by angle and distance ============================
305 | // @TODO can this be done in a more simple way?
306 | updateLineShapeVertices() {
307 | let distancePrevious, distanceCurrent;
308 |
309 | var vectorCurrent = new Vector3();
310 | var vectorPrevious = new Vector3();
311 | var vectorSide = new Vector3();
312 | var vectorSidePrevious = new Vector3();
313 | var vectorSideCopy = new Vector3();
314 |
315 | var lineVertices = this.lineVertices;
316 |
317 | for (let i = 0; i < this._resolution; i++) {
318 | this.lineShapeVertices[i][LEFT_LINE].copy(lineVertices[i]);
319 | this.lineShapeVertices[i][RIGHT_LINE].copy(lineVertices[i]);
320 |
321 | if (this.blur) {
322 | this.lineShapeVertices[i][LEFT_SMOOTH_LINE].copy(lineVertices[i]);
323 | this.lineShapeVertices[i][RIGHT_SMOOTH_LINE].copy(lineVertices[i]);
324 | }
325 |
326 | // previous point to current point ---------------------------------
327 | distancePrevious = 0.0;
328 | if (i > 0) {
329 | vectorPrevious.copy(lineVertices[i - 1]);
330 | vectorPrevious.sub(lineVertices[i]);
331 | distancePrevious = vectorPrevious.length();
332 | } else {
333 | if (this.closed) {
334 | vectorPrevious.copy(lineVertices[lineVertices.length - 1]);
335 | vectorPrevious.sub(lineVertices[i]);
336 | distancePrevious = vectorPrevious.length();
337 | } else {
338 | vectorPrevious.copy(0, 0, 0);
339 | }
340 | }
341 | if (distancePrevious > 0.0) {
342 | vectorPrevious.multiplyScalar(1.0 / distancePrevious); // normalize
343 | }
344 |
345 | // current point to next point -------------------------------------
346 | vectorCurrent.copy(lineVertices[i + 1]);
347 | vectorCurrent.sub(lineVertices[i]);
348 | distanceCurrent = vectorCurrent.length();
349 |
350 | if (distanceCurrent > 0.0) {
351 | vectorCurrent.multiplyScalar(1.0 / distanceCurrent); // normalize
352 | }
353 |
354 | if (this.angleBisection) {
355 | // calcuate angle bisection (good for 2d):
356 | vectorSide.copy(vectorCurrent);
357 | vectorSide.add(vectorPrevious);
358 |
359 | if ((distanceCurrent === 0 && distancePrevious === 0) || i === 0) {
360 | if (distanceCurrent === 0) {
361 | vectorCurrent.set(1, 0, 0);
362 | }
363 | if (this.closed) {
364 | vectorSide.copy(lineVertices[lineVertices.length - 1]);
365 | vectorSide.sub(lineVertices[0]);
366 | } else {
367 | vectorSide.copy(lineVertices[i + 1]);
368 | vectorSide.sub(lineVertices[i]);
369 | }
370 |
371 | vectorSide.set(-vectorSide.y, vectorSide.x, vectorSide.z);
372 | } else {
373 | // generate sideVector from upVector, if the sideVector could not be calculated from angle-bisection
374 | if (vectorSide.lengthSq() < 0.0001) {
375 | vectorSide.copy(this.upVector);
376 | vectorSide.cross(vectorCurrent);
377 | }
378 |
379 | vectorSidePrevious.copy(this.lineShapeVertices[i - 1][LEFT_LINE]);
380 | vectorSidePrevious.sub(lineVertices[i - 1]);
381 | if (vectorSide.dot(vectorSidePrevious) < 0) {
382 | vectorSide.negate();
383 | }
384 | }
385 | } else {
386 | this.calculateSideVector(vectorSide, vectorCurrent, vectorSidePrevious);
387 | }
388 |
389 | vectorSide.normalize();
390 | vectorSideCopy.copy(vectorSide);
391 |
392 | vectorSide.multiplyScalar(this._getLineWidth(i / this._resolution) / 2.0);
393 |
394 | this.lineShapeVertices[i][LEFT_LINE].add(vectorSide);
395 | this.lineShapeVertices[i][RIGHT_LINE].sub(vectorSide);
396 |
397 | if (this.blur) {
398 | const p = i / this._resolution;
399 | vectorSideCopy.multiplyScalar(
400 | this._getLineWidth(p) / 2.0 + this._getBlurWidth(p)
401 | );
402 |
403 | this.lineShapeVertices[i][LEFT_SMOOTH_LINE].add(vectorSideCopy);
404 | this.lineShapeVertices[i][RIGHT_SMOOTH_LINE].sub(vectorSideCopy);
405 | }
406 | }
407 |
408 | // add the end point ===================================================
409 | this.lineShapeVertices[this._resolution][LEFT_LINE].copy(
410 | lineVertices[this._resolution]
411 | );
412 | this.lineShapeVertices[this._resolution][RIGHT_LINE].copy(
413 | lineVertices[this._resolution]
414 | );
415 |
416 | if (this.blur) {
417 | this.lineShapeVertices[this._resolution][LEFT_SMOOTH_LINE].copy(
418 | lineVertices[this._resolution]
419 | );
420 | this.lineShapeVertices[this._resolution][RIGHT_SMOOTH_LINE].copy(
421 | lineVertices[this._resolution]
422 | );
423 | }
424 |
425 | // current point to next point -----------------------------------------
426 | vectorCurrent.copy(lineVertices[this._resolution]);
427 | vectorCurrent.sub(lineVertices[this._resolution - 1]);
428 | distanceCurrent = vectorCurrent.length();
429 | if (distanceCurrent > 0) {
430 | vectorCurrent.multiplyScalar(1.0 / distanceCurrent); // normalize
431 | }
432 |
433 | if (this.angleBisection) {
434 | // calcuate angle bisection (good for 2d):
435 |
436 | vectorSide.copy(vectorCurrent);
437 | vectorSide.set(-vectorSide.y, vectorSide.x, vectorSide.z);
438 |
439 | vectorSidePrevious.copy(
440 | this.lineShapeVertices[this._resolution - 1][LEFT_LINE]
441 | );
442 | vectorSidePrevious.sub(lineVertices[this._resolution - 1]);
443 | if (vectorSide.dot(vectorSidePrevious) < 0) {
444 | vectorSide.negate();
445 | }
446 | } else {
447 | this.calculateSideVector(vectorSide, vectorCurrent, vectorSidePrevious);
448 | }
449 |
450 | vectorSide.normalize();
451 | vectorSideCopy.copy(vectorSide);
452 |
453 | vectorSide.multiplyScalar(this._getLineWidth(1) / 2.0);
454 |
455 | this.lineShapeVertices[this._resolution][LEFT_LINE].add(vectorSide);
456 | this.lineShapeVertices[this._resolution][RIGHT_LINE].sub(vectorSide);
457 |
458 | if (this.blur) {
459 | vectorSideCopy.multiplyScalar(
460 | this._getLineWidth(1) / 2.0 + this._getBlurWidth(1)
461 | );
462 |
463 | this.lineShapeVertices[this._resolution][LEFT_SMOOTH_LINE].add(
464 | vectorSideCopy
465 | );
466 | this.lineShapeVertices[this._resolution][RIGHT_SMOOTH_LINE].sub(
467 | vectorSideCopy
468 | );
469 | }
470 |
471 | if (this.closed) {
472 | // this.lineShapeVertices[this._resolution] = this.lineShapeVertices[0];
473 |
474 | this.lineShapeVertices[this._resolution][LEFT_LINE].copy(
475 | this.lineShapeVertices[0][LEFT_LINE]
476 | );
477 | this.lineShapeVertices[this._resolution][RIGHT_LINE].copy(
478 | this.lineShapeVertices[0][RIGHT_LINE]
479 | );
480 | this.lineShapeVertices[this._resolution][LEFT_SMOOTH_LINE].copy(
481 | this.lineShapeVertices[0][LEFT_SMOOTH_LINE]
482 | );
483 | this.lineShapeVertices[this._resolution][RIGHT_SMOOTH_LINE].copy(
484 | this.lineShapeVertices[0][RIGHT_SMOOTH_LINE]
485 | );
486 | }
487 | }
488 |
489 | calculateSideVector(vectorSide, vectorCurrent, vectorSidePrevious) {
490 | // calculate side vector from upVector (better for 3d):
491 |
492 | vectorSide.copy(vectorCurrent);
493 | // if(Math.abs(this.upVector.z) === 1.0) {
494 | // vectorSide.z = 0;
495 | // }
496 | vectorSide.cross(this.upVector);
497 | // vectorSide.lerp(vectorSidePrevious, 0.95);
498 | if (vectorSide.lengthSq() < 0.01) {
499 | // use previous side vector, if current side vector is too short:
500 | vectorSide.copy(vectorSidePrevious);
501 | }
502 |
503 | vectorSidePrevious.copy(vectorSide);
504 | }
505 |
506 | getInterpolatedPoint(pos) {
507 | // position irrespective of length between points:
508 |
509 | let startIndex = 0;
510 | let delta = 0.0;
511 |
512 | var totalLength = this.length();
513 | let currentLength = 0.0;
514 | for (let i = 0; i < this.lineVertices.length - 1; i++) {
515 | let l = this.lineVertices[i].distanceTo(this.lineVertices[i + 1]);
516 | currentLength += l;
517 | if (currentLength / totalLength > pos) {
518 | startIndex = i;
519 |
520 | let x = pos * totalLength;
521 | let xStart = currentLength - l;
522 | let xEnd = currentLength;
523 |
524 | delta = (x - xStart) / (xEnd - xStart);
525 |
526 | break;
527 | }
528 | }
529 |
530 | var point = new Vector3();
531 |
532 | if (startIndex < this.lineVertices.length - 1) {
533 | point.copy(this.lineVertices[startIndex]);
534 | point.lerp(this.lineVertices[startIndex + 1], delta);
535 | }
536 | return point;
537 | }
538 |
539 | _getLineWidth(p) {
540 | if (this.lineWidthModifier) {
541 | return this.lineWidth * this.lineWidthModifier(p);
542 | } else {
543 | return this.lineWidth;
544 | }
545 | }
546 |
547 | _getBlurWidth(p) {
548 | if (this.blurWidthModifier) {
549 | return this.blurWidth * this.blurWidthModifier(p);
550 | } else {
551 | return this.blurWidth;
552 | }
553 | }
554 |
555 | _getColor(p) {
556 | if (this.colorModifier) {
557 | return this.color.clone().multiply(this.colorModifier(p));
558 | } else {
559 | return this.color;
560 | }
561 | }
562 |
563 | _getOpacity(p) {
564 | if (this.opacityModifier) {
565 | return this.opacity * this.opacityModifier(p);
566 | } else {
567 | return this.opacity;
568 | }
569 | }
570 |
571 | toString() {
572 | return 'BlurredLine';
573 | }
574 | }
575 |
576 | function updatePosition(positions, index, point) {
577 | positions[index] = point.x;
578 | positions[index + 1] = point.y;
579 | positions[index + 2] = point.z;
580 | }
581 |
582 | function updateColor(colors, index, color, alpha = 1) {
583 | colors[index] = color.r;
584 | colors[index + 1] = color.g;
585 | colors[index + 2] = color.b;
586 | colors[index + 3] = alpha;
587 | }
588 |
589 | export class BlurredLineMaterial extends RawShaderMaterial {
590 | constructor(parameters) {
591 | super();
592 |
593 | if (!parameters) {
594 | parameters = {
595 | depthTest: false,
596 | side: DoubleSide,
597 | transparent: true,
598 | };
599 | } else {
600 | if (parameters.depthTest === undefined) parameters.depthTest = false;
601 | if (parameters.side === undefined) parameters.side = DoubleSide;
602 | if (parameters.transparent === undefined) parameters.transparent = true;
603 | }
604 |
605 | this.uniforms = {
606 | materialColor: { value: new Color(0xffffff) },
607 | opacity: { value: 1.0 },
608 | };
609 |
610 | this.color = new Color();
611 |
612 | this.vertexShader = `
613 | precision mediump float;
614 | precision mediump int;
615 |
616 | uniform mat4 modelViewMatrix; // optional
617 | uniform mat4 projectionMatrix; // optional
618 |
619 | attribute vec3 position;
620 | attribute vec4 color;
621 |
622 | uniform vec3 materialColor;
623 | uniform float opacity;
624 |
625 | // varying vec3 vPosition;
626 | varying vec4 vColor;
627 |
628 | void main() {
629 |
630 | // vPosition = position;
631 | vColor = color * vec4( materialColor, opacity );
632 |
633 | gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
634 |
635 | }
636 | `;
637 |
638 | this.fragmentShader = `
639 | precision mediump float;
640 | precision mediump int;
641 |
642 | // uniform float time;
643 | // uniform float color;
644 | // uniform float opacity;
645 |
646 | // varying vec3 vPosition;
647 | varying vec4 vColor;
648 |
649 | void main() {
650 |
651 | vec4 c = vColor;
652 | // c.a *= opacity;
653 | // color.r += sin( vPosition.x * 10.0 + time ) * 0.5;
654 |
655 | gl_FragColor = c;
656 |
657 | }
658 | `;
659 |
660 | this.type = 'BlurredLineMaterial';
661 |
662 | Object.defineProperties(this, {
663 | color: {
664 | enumerable: true,
665 | get: function () {
666 | return this.uniforms.materialColor.value;
667 | },
668 | set: function (value) {
669 | this.uniforms.materialColor.value = value;
670 | },
671 | },
672 | opacity: {
673 | enumerable: true,
674 | get: function () {
675 | return this.uniforms.opacity.value;
676 | },
677 | set: function (value) {
678 | this.uniforms.opacity.value = value;
679 | },
680 | },
681 | });
682 |
683 | this.setValues(parameters);
684 | }
685 |
686 | copy(source) {
687 | ShaderMaterial.prototype.copy.call(this, source);
688 |
689 | this.color.copy(source.color);
690 | this.opacity = source.opacity;
691 |
692 | return this;
693 | }
694 | }
695 |
--------------------------------------------------------------------------------