├── .babelrc ├── .gitignore ├── README.md ├── api.md ├── code_examples.md ├── config ├── gulpfile.js └── webpack.config.js ├── demo └── exhibition │ ├── antd-mobile.min.css │ ├── antd.min.css │ ├── bundle.js │ ├── bundle.js.map │ ├── down.png │ ├── index.html │ ├── index.js │ ├── left.png │ ├── right.png │ ├── tweed.png │ ├── up.png │ └── webpack.config.js ├── dist ├── svg-3d-builder.min.js └── svg-3d-builder.min.js.map ├── lib ├── index.js └── util.js ├── package.json └── src ├── es5.js ├── index.js └── util.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["env"] 3 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | test/ 2 | node_modules 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SVG 3D Builder 2 | 3 | [![npm version](https://img.shields.io/npm/v/svg-3d-builder.svg?style=flat-square)](https://www.npmjs.com/package/svg-3d-builder) 4 | 5 |
6 | 7 | 8 | 9 |
10 | 11 | This framework aims at creating 3d models with **SVG** and to provide a concise API. It is purely developed with concepts of two-dimensions. 12 | One of its essential implementations is **Bezier** in both curve and surface. 13 | It is one thing to describe them with mathematic equations, but another thing to illustrate them with computer graphics. 14 | 15 | See [online exhibition](https://libcafe.com/3d/index.html) developed by the framework and its [source code](https://github.com/captainwz/svg-3d-builder/tree/master/demo/exhibition). 16 | 17 | ## Start 18 | You can either start it in [traditional way](https://github.com/captainwz/svg-3d-builder/blob/master/dist/svg-3d-builder.min.js) 19 | ```html 20 | 21 | ``` 22 | Or embark your development with `ES6` 23 | ```shell 24 | npm install --save svg-3d-builder 25 | ``` 26 | ```javascript 27 | import Builder from 'svg-3d-builder'; 28 | ``` 29 | Make sure there is an svg element described in your document 30 | ```html 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | ``` 40 | And see your simplest work by adding these codes 41 | ```javascript 42 | Builder 43 | .select('#graph') 44 | .drawLine('M 0 0 0 l 100 0 0') 45 | .action(); 46 | ``` 47 | 48 | **Of course you can see [code examples](https://github.com/captainwz/svg-3d-builder/tree/master/code_examples.md) before building sophisticated works by yourself.** 49 | 50 | 51 | ## API 52 | You also need to look up the [API document](https://github.com/captainwz/svg-3d-builder/tree/master/api.md). 53 | 54 | ## Lisence 55 | Apache 56 | 57 | ## Other 58 | [postscript](https://libcafe.com/2018/08/25/3d-builder/) 59 | 60 | 61 | -------------------------------------------------------------------------------- /api.md: -------------------------------------------------------------------------------- 1 | # API 2 | 3 | Some 3d concepts are prerequisite. The related links will be attached in the followings. 4 | 5 | ## Basic 6 | 7 | --- 8 | ```javascript 9 | .select(selector) 10 | ``` 11 | property|type|description|required|default 12 | :-:| :-: | :-: |:-: | :-: 13 | selector|String|svg element selector|true|| 14 | 15 | 16 | --- 17 | 18 | ```javascript 19 | .setCamera({anchor, d, alpha, beta}) 20 | ``` 21 | 22 | This framework is using [perspective projection](https://en.wikipedia.org/wiki/3D_projection) to map three-dimensional points into the two-dimensional graph. In `perspective projection`, we need to locate the **sight point** (equivalent to a camera) . Any sight point can be specified with four elements: `anchor`, `d`, `alpha`, `beta`. 23 | 24 | property|type|description|required|default 25 | :-:| :-: | :-: |:-: | :-: 26 | anchor|Array|the camera always needs a focused point|false|[0, 0, 0] 27 | d| Number| the distance between the camera and the anchor| false| 300 28 | alpha| Number| image that the camera is rotating around the anchor. The track can be described as rotating `alpha` horizontally and `beta` vertically| false| 0 29 | beta| Number| image that the camera is rotating around the anchor. The track can be described as rotating `alpha` horizontally and `beta` vertically| false| 0 30 | 31 | --- 32 | 33 | ```javascript 34 | .setScreen({ratio, offsetX, offsetY}) 35 | ``` 36 | 37 | In `perspective projection`, there is also a screen. 38 | 39 | property|type|description|required|default 40 | :-:| :-: | :-: |:-: | :-: 41 | ratio|Number|the ratio of the distance between the screen and the anchor to the distance between the camera and the anchor| false| 0.5 42 | offsetX| Number| x offset of the axis| false| 0 43 | offsetY| Number| y offset of the axis| false| 0 44 | 45 | --- 46 | 47 | ```javascript 48 | .setAxis({show, xLength, xColor, yLength, yColor, zLength, zColor}) 49 | ``` 50 | 51 | Set axis. 52 | 53 | property|type|description|required|default 54 | :-:| :-: | :-: |:-: | :-: 55 | show|Boolean|if show axis|false|false 56 | xLength|Number|how long is x-axis displayed|false|200 57 | xColor|String|what color is x-axis displayed|false|'#000' 58 | yLength|Number|how long is y-axis displayed|false|200 59 | yColor|String|what color is y-axis displayed|false|'#000' 60 | zLength|Number|how long is z-axis displayed|false|200 61 | zColor|String|what color is z-axis displayed|false|'#000' 62 | 63 | --- 64 | 65 | ```javascript 66 | .action() 67 | ``` 68 | Draw the graph. 69 | 70 | --- 71 | 72 | ```javascript 73 | .reset() 74 | ``` 75 | 76 | Clean the graph and reset all setting options. 77 | 78 | --- 79 | 80 | 81 | ## Bézier Triangle 82 | 83 | References about [bézier triangle](https://en.wikipedia.org/wiki/B%C3%A9zier_triangle). 84 | 85 | --- 86 | 87 | ```javascript 88 | .drawBezierTriangle(control, density) 89 | ``` 90 | property|type|description|required|default 91 | :-:| :-: | :-: |:-: | :-: 92 | control|Object|10 coordinates in form of {a3, a2b, ab2, b3, b2y, by2, y3, ay2, a2y, aby}|true|| 93 | density|Number| density of the surface.|true|| 94 | 95 | --- 96 | 97 | ```javascript 98 | .redrawBezierTriangle(control, density, index) 99 | ``` 100 | property|type|description|required|default 101 | :-:| :-: | :-: |:-: | :-: 102 | control|Object|10 coordinates in form of {a3, a2b, ab2, b3, b2y, by2, y3, ay2, a2y, aby}|true|| 103 | density|Number| density of the surface.|true|| 104 | index|Number| index of the surface. Refer to the latest one if not set|false|| 105 | 106 | 107 | --- 108 | 109 | ```javascript 110 | .setBezierTriangleStroke(color, index) 111 | ``` 112 | property|type|description|required|default 113 | :-:| :-: | :-: |:-: | :-: 114 | color|String|color of stroke|true|| 115 | index|Number| index of the surface. Refer to the latest one if not set|false|| 116 | 117 | --- 118 | 119 | ```javascript 120 | .setBezierTriangleStrokeWidth(width, index) 121 | ``` 122 | 123 | property|type|description|required|default 124 | :-:| :-: | :-: |:-: | :-: 125 | width|String|width of stroke|true|| 126 | index|Number| index of the surface. Refer to the latest one if not set|false|| 127 | 128 | --- 129 | 130 | ```javascript 131 | .setBezierTriangleFill(color, index) 132 | ``` 133 | property|type|description|required|default 134 | :-:| :-: | :-: |:-: | :-: 135 | color|String|color of triangle|true|| 136 | index|Number| index of the surface. Refer to the latest one if not set|false|| 137 | 138 | --- 139 | 140 | ## Bézier Surface 141 | 142 | References about [bézier surface](https://en.wikipedia.org/wiki/B%C3%A9zier_surface) 143 | 144 | --- 145 | 146 | ```javascript 147 | .drawBezierSurface(matrix, density) 148 | ``` 149 | 150 | property|type|description|required|default 151 | :-:| :-: | :-: |:-: | :-: 152 | matrix|Array|4*4 Array of control points|true|| 153 | density|Number| density of the surface. Surface will be smoother as density is larger. **A number less than 10 is adviced.** | true| | 154 | 155 | --- 156 | 157 | ```javascript 158 | .redrawBezierSurface(matrix, density, index) 159 | ``` 160 | 161 | Redraw bezier surface. 162 | 163 | property|type|description|required|default 164 | :-:| :-: | :-: |:-: | :-: 165 | matrix|Array|4*4 Array of control points|true|| 166 | density|Number| density of the surface.|true|| 167 | index|Number| index of the surface. Refer to the latest one if not set|false|| 168 | 169 | --- 170 | 171 | ```javascript 172 | .setBezierSurfaceStroke(color, index) 173 | ``` 174 | 175 | property|type|description|required|default 176 | :-:| :-: | :-: |:-: | :-: 177 | color|String|color of stroke|true|| 178 | index|Number|index of the bezier surface. If not set, the latest bezier surface will be painted|false|| 179 | 180 | --- 181 | 182 | ```javascript 183 | .setBezierSurfaceStrokeWidth(width, index) 184 | ``` 185 | 186 | property|type|description|required|default 187 | :-:| :-: | :-: |:-: | :-: 188 | width|String|width of stroke|true|| 189 | index|Number| index of the surface. Refer to the latest one if not set|false|| 190 | 191 | --- 192 | 193 | ```javascript 194 | .setBezierSurfaceFill(color, index) 195 | ``` 196 | property|type|description|required|default 197 | :-:| :-: | :-: |:-: | :-: 198 | color|String|color of triangle|true|| 199 | index|Number| index of the surface. Refer to the latest one if not set|false|| 200 | 201 | --- 202 | 203 | ## Bézier Curve 204 | 205 | References about [bézier curve](https://en.wikipedia.org/wiki/B%C3%A9zier_curve) 206 | 207 | --- 208 | 209 | ```javascript 210 | .drawBezierCurve(p0, p1, p2, p3, n) 211 | ``` 212 | 213 | property|type|description|required|default 214 | :-:| :-: | :-: |:-: | :-: 215 | p0| Array| control point| true| | 216 | p1| Array| control point| true| | 217 | p2| Array| control point| true| | 218 | p3| Array| control point| true| | 219 | n| Number| Number of points to calculate. The curve will be smoother as `n` is larger| true| | 220 | 221 | --- 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | ```javascript 230 | .drawLine(d) 231 | ``` 232 | 233 | Draw a line. 234 | 235 | property|type|description|required|default 236 | :-:| :-: | :-: |:-: | :-: 237 | d|String|path attribute|true|| 238 | 239 | --- 240 | 241 | ```javascript 242 | .setBezierCurveStroke(color, index) 243 | ``` 244 | 245 | property|type|description|required|default 246 | :-:| :-: | :-: |:-: | :-: 247 | color|String|color of the bezier curve|true|| 248 | index|Number|index of the bezier curve. If not set, the latest bezier curve will be painted|false|| 249 | 250 | --- 251 | 252 | 253 | 254 | 255 | --- 256 | 257 | 258 | 259 | --- 260 | 261 | ```javascript 262 | .setLineStroke(color, index) 263 | ``` 264 | Set color of a line. 265 | 266 | property|type|description|required|default 267 | :-:| :-: | :-: |:-: | :-: 268 | color|String|color of the line|true|| 269 | index|Number|index of the line. If not set, the latest line will be painted|false|| 270 | 271 | --- 272 | 273 | 274 | 275 | 276 | ```javascript 277 | .redrawLine(d, index) 278 | ``` 279 | 280 | Redraw line. 281 | 282 | property|type|description|required|default 283 | :-:| :-: | :-: |:-: | :-: 284 | d|String|path attribute|true|| 285 | index|Number| index of the surface. Refer to the latest one if not set|false| 286 | 287 | --- 288 | 289 | ```javascript 290 | .redrawBezierCurve(p0, p1, p2, p3, n, index) 291 | ``` 292 | 293 | Redraw bezier curve. 294 | 295 | property|type|description|required|default 296 | :-:| :-: | :-: |:-: | :-: 297 | p0| Array| control point| true| | 298 | p1| Array| control point| true| | 299 | p2| Array| control point| true| | 300 | p3| Array| control point| true| | 301 | n| Number| Number of points to calculate. The curve will be smoother as `n` is larger| true| | 302 | index|Number| index of the surface. Refer to the latest one if not set|false|| 303 | 304 | 305 | 306 | 307 | 308 | --- 309 | 310 | 311 | -------------------------------------------------------------------------------- /code_examples.md: -------------------------------------------------------------------------------- 1 | # Chromatic 3D Heart 2 | 3 | 4 | 5 | ```javascript 6 | Builder 7 | .reset() 8 | .select('#space') 9 | .setCamera({anchor: [0, 30, 0], d: 1000000000}) 10 | .setScreen({ratio: 0.99, offsetX: screenWidth/2, offsetY: screenHeight/2.5}) 11 | .drawBezierTriangle({ 12 | a3: [-100, -100, 0], 13 | a2b: [-30, -120, 0], 14 | ab2: [-20, -90, 0], 15 | b3: [0, -30, 0], 16 | b2y: [0, 30, 60], 17 | by2: [0, 60, 60], 18 | y3: [0, 200, 0], 19 | a2y: [-230, -50, 0], 20 | ay2: [-200, 30, 0], 21 | aby: [-30, 30, 100] 22 | }, 9) 23 | .setBezierTriangleFill('#ff756a') 24 | .setBezierTriangleStroke('#fdd6a6') 25 | .setBezierTriangleStrokeWidth(0.1) 26 | .drawBezierTriangle({ 27 | a3: [100, -100, 0], 28 | a2b: [30, -120, 0], 29 | ab2: [20, -90, 0], 30 | b3: [0, -30, 0], 31 | b2y: [0, 30, 60], 32 | by2: [0, 60, 60], 33 | y3: [0, 200, 0], 34 | a2y: [230, -50, 0], 35 | ay2: [200, 30, 0], 36 | aby: [30, 30, 100] 37 | }, 9) 38 | .setBezierTriangleFill('#ff756a') 39 | .setBezierTriangleStroke('#fdd6a6') 40 | .setBezierTriangleStrokeWidth(0.1) 41 | .drawBezierTriangle({ 42 | a3: [-100, -100, 0], 43 | a2b: [-30, -120, 0], 44 | ab2: [-20, -90, 0], 45 | b3: [0, -30, 0], 46 | b2y: [0, 30, -60], 47 | by2: [0, 60, -60], 48 | y3: [0, 200, 0], 49 | a2y: [-230, -50, 0], 50 | ay2: [-200, 30, 0], 51 | aby: [-30, 30, -100] 52 | }, 9) 53 | .setBezierTriangleFill('#ffa69e') 54 | .setBezierTriangleStroke('#ff798b') 55 | .setBezierTriangleStrokeWidth(0.1) 56 | .drawBezierTriangle({ 57 | a3: [100, -100, 0], 58 | a2b: [30, -120, 0], 59 | ab2: [20, -90, 0], 60 | b3: [0, -30, 0], 61 | b2y: [0, 30, -60], 62 | by2: [0, 60, -60], 63 | y3: [0, 200, 0], 64 | a2y: [230, -50, 0], 65 | ay2: [200, 30, 0], 66 | aby: [30, 30, -100] 67 | }, 9) 68 | .setBezierTriangleFill('#ffa69e') 69 | .setBezierTriangleStroke('#ff798b') 70 | .setBezierTriangleStrokeWidth(0.1) 71 | .action() 72 | ``` 73 | 74 | # Bezier Surface 75 | 76 | 77 | 78 | ```javascript 79 | Builder 80 | .reset() 81 | .select('#space') 82 | .setCamera({anchor: [100, 100, 0], alpha: Math.PI/6}) 83 | .setScreen({ratio: 0.99, offsetX: screenWidth/2, offsetY: screenHeight/2.5}) 84 | .setAxis({ 85 | show: true, 86 | xLength: 260, 87 | xColor: '#108ee9', 88 | yLength: 260, 89 | yColor: '#108ee9', 90 | zLength: 200, 91 | zColor: '#108ee9' 92 | }) 93 | .drawBezierSurface( 94 | [ 95 | [[0,0,0], [25, 30, 50], [75, 78, 50], [200, 0, 0]], 96 | [[0,50,0], [25, 25, 50], [75, 25, 50], [100, 50, 0]], 97 | [[0,150,0], [25, 150, 50], [75, 150, 50], [100, 50, 0]], 98 | [[150, 200,0], [150, 200, 0], [150, 200, 0], [150, 200, 0]], 99 | 100 | ] 101 | , 9) 102 | .setBezierSurfaceStroke('rgb(143, 221, 195)') 103 | .action(); 104 | ``` 105 | 106 | # Bezier Surface In Animation 107 | 108 | 109 | 110 | ```javascript 111 | Builder 112 | .reset() 113 | .select('#space') 114 | .setCamera({anchor: [100, 100, 0], alpha: Math.PI/ 6}) 115 | .setScreen({ratio: 0.99, offsetX: screenWidth/2, offsetY: screenHeight/2.5}) 116 | .drawBezierSurface( 117 | [ 118 | [[0,0,0], [50, 0, 0], [150, 0, 0], [200, 0, 0]], 119 | [[0,50,0], [50, 50, 0], [150, 50, 0], [200, 50, 0]], 120 | [[0,150,0], [50, 150, 0], [150, 150, 0], [200, 150, 0]], 121 | [[0, 200,0], [50, 200, 0], [150, 200, 0], [200, 200, 0]], 122 | 123 | ] 124 | , 8) 125 | .setBezierSurfaceStroke('rgb(143, 221, 195)') 126 | .action(); 127 | 128 | let b = 0; 129 | let d = 2; 130 | 131 | 132 | var theInterval = setInterval(() => { 133 | 134 | Builder 135 | .redrawBezierSurface( 136 | [ 137 | [[0,0,0], [50, 0 + b/4, 0 + b/4], [150, 0 + b/4, 0 + b/4], [200, 0, 0]], 138 | [[0,50,0], [50, 50 + b/3, 0 + b/3], [150, 50 + b/3, 0 + b/3], [200, 50, 0]], 139 | [[0,150,0], [50, 150 + b/2, 0 + b/2], [150, 150 + b/2, 0 + b/2], [200, 150, 0]], 140 | [[0, 200,0], [50, 200 + b, 0 + b], [150, 200 + b, 0 + b], [200, 200, 0]], 141 | 142 | ] 143 | , 8) 144 | .action() 145 | 146 | if (d > 0) { 147 | b += d; 148 | } else { 149 | b += 10*d; 150 | } 151 | 152 | 153 | if (b >= 100 || b <= 0) { 154 | d = -d; 155 | } 156 | 157 | }, 150) 158 | ``` 159 | -------------------------------------------------------------------------------- /config/gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var babel = require('gulp-babel'); 3 | 4 | gulp.task('default', function() { 5 | gulp.src(['../src/index.js', '../src/util.js']) 6 | .pipe(babel()) 7 | .pipe(gulp.dest('../lib')) 8 | }); -------------------------------------------------------------------------------- /config/webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | 3 | module.exports = { 4 | entry: path.resolve(__dirname, '../src/es5.js'), 5 | output: { 6 | path: path.resolve(__dirname, '../dist'), 7 | filename: 'svg-3d-builder.min.js' 8 | }, 9 | resolve: { 10 | extensions: ['.js'] 11 | }, 12 | module: { 13 | rules: [ 14 | { 15 | test: /\.js$/, 16 | exclude: [/node_modules/], 17 | loader: 'babel-loader', 18 | options: { 19 | presets: ['stage-0', 'env'] 20 | }, 21 | } 22 | ] 23 | }, 24 | devtool: "source-map" 25 | } -------------------------------------------------------------------------------- /demo/exhibition/down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/captainwz/svg-3d-builder/336c8094f2f024b55b05ad8a95e3de5e1ae8247b/demo/exhibition/down.png -------------------------------------------------------------------------------- /demo/exhibition/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 3D Builder 5 | 6 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /demo/exhibition/index.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { Button } from 'antd-mobile'; 4 | import Builder from '../../lib/index'; 5 | import { rgb } from 'd3-color'; 6 | import { interpolateRgbBasis } from 'd3-interpolate'; 7 | import Menu from 'antd/lib/menu'; 8 | import Icon from 'antd/lib/icon'; 9 | 10 | class App extends Component { 11 | constructor (props) { 12 | super(props); 13 | this.state = { 14 | screenWidth: window.screen.width, 15 | screenHeight: window.screen.height, 16 | alpha: 0, 17 | beta: 0, 18 | upInt: null, 19 | upActive: false, 20 | leftInt: null, 21 | leftActive: false, 22 | rightInt: null, 23 | rightActive: false, 24 | downInt: null, 25 | downActive: false, 26 | selectedKey: 'chromatic-3d' 27 | } 28 | } 29 | 30 | drawSphere () { 31 | 32 | this.setState({ 33 | alpha: 0, 34 | beta: 0, 35 | upInt: null, 36 | upActive: false, 37 | leftInt: null, 38 | leftActive: false, 39 | rightInt: null, 40 | rightActive: false, 41 | downInt: null, 42 | downActive: false 43 | }) 44 | 45 | const drawCircle0 = (x, r) => { 46 | let lineStr = 'M '; 47 | let alpha = 0; 48 | for (; alpha < Math.PI * 2 ; alpha += Math.PI/180) { 49 | 50 | if (alpha != 0) { 51 | lineStr += 'L '; 52 | } 53 | 54 | lineStr += `${x} ${Math.cos(alpha)*r} ${Math.sin(alpha)*r} ` 55 | 56 | } 57 | lineStr += 'z'; 58 | 59 | Builder 60 | .drawLine(lineStr) 61 | .setLineStroke('rgb(143, 221, 195)') 62 | 63 | } 64 | 65 | const drawCircle1 = (y, r) => { 66 | let lineStr = 'M '; 67 | let alpha = 0; 68 | for (; alpha < Math.PI * 2 ; alpha += Math.PI/180) { 69 | 70 | if (alpha != 0) { 71 | lineStr += 'L '; 72 | } 73 | 74 | lineStr += `${Math.cos(alpha)*r} ${y} ${Math.sin(alpha)*r} ` 75 | 76 | } 77 | lineStr += 'z'; 78 | 79 | Builder 80 | .drawLine(lineStr) 81 | .setLineStroke('rgb(143, 221, 195)') 82 | 83 | } 84 | 85 | const drawCircle2 = (r, alpha) => { 86 | 87 | let lineStr = 'M '; 88 | let beta = 0; 89 | for (; beta < Math.PI * 2 ; beta += Math.PI/180) { 90 | 91 | if (beta != 0) { 92 | lineStr += 'L '; 93 | } 94 | 95 | lineStr += `${Math.cos(alpha)*Math.abs(r*Math.cos(beta))} ${r*Math.sin(beta)} ${Math.sin(alpha)*Math.abs(r*Math.cos(beta))} ` 96 | 97 | } 98 | lineStr += 'z'; 99 | 100 | Builder 101 | .drawLine(lineStr) 102 | .setLineStroke('rgb(143, 221, 195)') 103 | 104 | } 105 | 106 | Builder 107 | .reset() 108 | .select('#space') 109 | .setScreen({ratio: 0.99, offsetX: this.state.screenWidth/2, offsetY: this.state.screenHeight/2.5}) 110 | .setCamera({anchor: [0, 0, 0], d: 10000}) 111 | 112 | let k = 0; 113 | 114 | for (; k < 100; k += 20) { 115 | // drawCircle0(k, Math.sqrt(100*100 - k*k)); 116 | // drawCircle0(-k, Math.sqrt(100*100 - k*k)); 117 | drawCircle1(k, Math.sqrt(100*100 - k*k)); 118 | drawCircle1(-k, Math.sqrt(100*100 - k*k)); 119 | } 120 | 121 | let alpha = 0 ; 122 | 123 | for (; alpha < Math.PI*2; alpha += Math.PI/18) { 124 | drawCircle2(100, alpha); 125 | } 126 | 127 | Builder.action(); 128 | 129 | 130 | 131 | } 132 | 133 | drawLine () { 134 | 135 | this.setState({ 136 | alpha: 0, 137 | beta: 0, 138 | upInt: null, 139 | upActive: false, 140 | leftInt: null, 141 | leftActive: false, 142 | rightInt: null, 143 | rightActive: false, 144 | downInt: null, 145 | downActive: false 146 | }) 147 | 148 | Builder 149 | .reset() 150 | .select('#space') 151 | .setScreen({ratio: 0.99, offsetX: this.state.screenWidth/2, offsetY: this.state.screenHeight/2.5}) 152 | .setCamera({anchor: [100, 100, 0]}) 153 | .drawLine('M 0 100 0 l 100 -100 0') 154 | .setLineStroke('rgb(143, 221, 195)') 155 | .action(); 156 | 157 | } 158 | 159 | drawCube () { 160 | 161 | this.setState({ 162 | alpha: 0, 163 | beta: 0, 164 | upInt: null, 165 | upActive: false, 166 | leftInt: null, 167 | leftActive: false, 168 | rightInt: null, 169 | rightActive: false, 170 | downInt: null, 171 | downActive: false 172 | }) 173 | 174 | Builder 175 | .reset() 176 | .select('#space') 177 | .setScreen({ratio: 0.99, offsetX: this.state.screenWidth/2, offsetY: this.state.screenHeight/2.5}) 178 | .drawLine('M -50 -50 -50 l 100 0 0 l 0 100 0 l -100 0 0 z') 179 | .setLineStroke('rgb(143, 221, 195)') 180 | .drawLine('M -50 -50 -50 l 0 0 100 l 0 100 0 l 0 0 -100 z') 181 | .setLineStroke('rgb(143, 221, 195)') 182 | .drawLine('M -50 -50 50 l 100 0 0 l 0 100 0 l -100 0 0 z') 183 | .setLineStroke('rgb(143, 221, 195)') 184 | .drawLine('M 50 -50 50 l 0 0 -100 l 0 100 0 l 0 0 100 z') 185 | .setLineStroke('rgb(143, 221, 195)') 186 | .action(); 187 | 188 | } 189 | 190 | 191 | 192 | drawBezierCurve () { 193 | 194 | this.setState({ 195 | alpha: 0, 196 | beta: 0, 197 | upInt: null, 198 | upActive: false, 199 | leftInt: null, 200 | leftActive: false, 201 | rightInt: null, 202 | rightActive: false, 203 | downInt: null, 204 | downActive: false 205 | }) 206 | 207 | Builder 208 | .reset() 209 | .select('#space') 210 | .setScreen({ratio: 0.99, offsetX: this.state.screenWidth/2, offsetY: this.state.screenHeight/2.5}) 211 | .setCamera({anchor: [100, 0, 0]}) 212 | .drawBezierCurve([0,0,0], [0, 100, 0], [200, 100, 0], [200, 0, 0], 50) 213 | .setBezierCurveStroke('rgb(143, 221, 195)') 214 | .action(); 215 | 216 | } 217 | 218 | up () { 219 | this.state.beta += Math.PI/90; 220 | 221 | Builder 222 | .setCamera({beta: this.state.beta}) 223 | .action(); 224 | } 225 | 226 | down () { 227 | this.state.beta -= Math.PI/90; 228 | 229 | Builder 230 | .setCamera({beta: this.state.beta}) 231 | .action(); 232 | } 233 | 234 | left () { 235 | this.state.alpha -= Math.PI/90; 236 | 237 | Builder 238 | .setCamera({alpha: this.state.alpha}) 239 | .action(); 240 | } 241 | 242 | right () { 243 | this.state.alpha += Math.PI/90; 244 | 245 | Builder 246 | .setCamera({alpha: this.state.alpha}) 247 | .action(); 248 | } 249 | 250 | drawBezierSurface () { 251 | 252 | this.setState({ 253 | alpha: Math.PI/6, 254 | beta: 0, 255 | upInt: null, 256 | upActive: false, 257 | leftInt: null, 258 | leftActive: false, 259 | rightInt: null, 260 | rightActive: false, 261 | downInt: null, 262 | downActive: false 263 | }) 264 | 265 | Builder 266 | .reset() 267 | .select('#space') 268 | .setCamera({anchor: [100, 100, 0], alpha: Math.PI/6}) 269 | .setScreen({ratio: 0.99, offsetX: this.state.screenWidth/2, offsetY: this.state.screenHeight/2.5}) 270 | .setAxis({ 271 | show: true, 272 | xLength: 260, 273 | xColor: '#108ee9', 274 | yLength: 260, 275 | yColor: '#108ee9', 276 | zLength: 200, 277 | zColor: '#108ee9' 278 | }) 279 | .drawBezierSurface( 280 | [ 281 | [[0,0,0], [25, 30, 50], [75, 78, 50], [200, 0, 0]], 282 | [[0,50,0], [25, 25, 50], [75, 25, 50], [100, 50, 0]], 283 | [[0,150,0], [25, 150, 50], [75, 150, 50], [100, 50, 0]], 284 | [[150, 200,0], [150, 200, 0], [150, 200, 0], [150, 200, 0]], 285 | 286 | ] 287 | , 9) 288 | .setBezierSurfaceStroke('rgb(143, 221, 195)') 289 | .action(); 290 | 291 | } 292 | 293 | drawBezierSurfaceAnimation () { 294 | 295 | this.setState({ 296 | alpha: Math.PI/ 6, 297 | beta: 0, 298 | upInt: null, 299 | upActive: false, 300 | leftInt: null, 301 | leftActive: false, 302 | rightInt: null, 303 | rightActive: false, 304 | downInt: null, 305 | downActive: false 306 | }) 307 | 308 | Builder 309 | .reset() 310 | .select('#space') 311 | .setCamera({anchor: [100, 100, 0], alpha: Math.PI/ 6}) 312 | .setScreen({ratio: 0.99, offsetX: this.state.screenWidth/2, offsetY: this.state.screenHeight/2.5}) 313 | .drawBezierSurface( 314 | [ 315 | [[0,0,0], [50, 0, 0], [150, 0, 0], [200, 0, 0]], 316 | [[0,50,0], [50, 50, 0], [150, 50, 0], [200, 50, 0]], 317 | [[0,150,0], [50, 150, 0], [150, 150, 0], [200, 150, 0]], 318 | [[0, 200,0], [50, 200, 0], [150, 200, 0], [200, 200, 0]], 319 | 320 | ] 321 | , 8) 322 | .setBezierSurfaceStroke('rgb(143, 221, 195)') 323 | .action(); 324 | 325 | let b = 0; 326 | let d = 2; 327 | 328 | 329 | this.globalInt = setInterval(() => { 330 | 331 | Builder 332 | .redrawBezierSurface( 333 | [ 334 | [[0,0,0], [50, 0 + b/4, 0 + b/4], [150, 0 + b/4, 0 + b/4], [200, 0, 0]], 335 | [[0,50,0], [50, 50 + b/3, 0 + b/3], [150, 50 + b/3, 0 + b/3], [200, 50, 0]], 336 | [[0,150,0], [50, 150 + b/2, 0 + b/2], [150, 150 + b/2, 0 + b/2], [200, 150, 0]], 337 | [[0, 200,0], [50, 200 + b, 0 + b], [150, 200 + b, 0 + b], [200, 200, 0]], 338 | 339 | ] 340 | , 8) 341 | .action() 342 | 343 | if (d > 0) { 344 | b += d; 345 | } else { 346 | b += 10*d; 347 | } 348 | 349 | 350 | if (b >= 100 || b <= 0) { 351 | d = -d; 352 | } 353 | 354 | }, 150) 355 | 356 | } 357 | 358 | drawChromatic3D () { 359 | 360 | this.setState({ 361 | alpha: 0, 362 | beta: 0, 363 | upInt: null, 364 | upActive: false, 365 | leftInt: null, 366 | leftActive: false, 367 | rightInt: null, 368 | rightActive: false, 369 | downInt: null, 370 | downActive: false 371 | }) 372 | 373 | Builder 374 | .reset() 375 | .select('#space') 376 | .setCamera({anchor: [0, 30, 0], d: 1000000000}) 377 | .setScreen({ratio: 0.99, offsetX: this.state.screenWidth/2, offsetY: this.state.screenHeight/2.5}) 378 | .drawBezierTriangle({ 379 | a3: [-100, -100, 0], 380 | a2b: [-30, -120, 0], 381 | ab2: [-20, -90, 0], 382 | b3: [0, -30, 0], 383 | b2y: [0, 30, 60], 384 | by2: [0, 60, 60], 385 | y3: [0, 200, 0], 386 | a2y: [-230, -50, 0], 387 | ay2: [-200, 30, 0], 388 | aby: [-30, 30, 100] 389 | }, 9) 390 | .setBezierTriangleFill('#ff756a') 391 | .setBezierTriangleStroke('#fdd6a6') 392 | .setBezierTriangleStrokeWidth(0.1) 393 | .drawBezierTriangle({ 394 | a3: [100, -100, 0], 395 | a2b: [30, -120, 0], 396 | ab2: [20, -90, 0], 397 | b3: [0, -30, 0], 398 | b2y: [0, 30, 60], 399 | by2: [0, 60, 60], 400 | y3: [0, 200, 0], 401 | a2y: [230, -50, 0], 402 | ay2: [200, 30, 0], 403 | aby: [30, 30, 100] 404 | }, 9) 405 | .setBezierTriangleFill('#ff756a') 406 | .setBezierTriangleStroke('#fdd6a6') 407 | .setBezierTriangleStrokeWidth(0.1) 408 | .drawBezierTriangle({ 409 | a3: [-100, -100, 0], 410 | a2b: [-30, -120, 0], 411 | ab2: [-20, -90, 0], 412 | b3: [0, -30, 0], 413 | b2y: [0, 30, -60], 414 | by2: [0, 60, -60], 415 | y3: [0, 200, 0], 416 | a2y: [-230, -50, 0], 417 | ay2: [-200, 30, 0], 418 | aby: [-30, 30, -100] 419 | }, 9) 420 | .setBezierTriangleFill('#ffa69e') 421 | .setBezierTriangleStroke('#ff798b') 422 | .setBezierTriangleStrokeWidth(0.1) 423 | .drawBezierTriangle({ 424 | a3: [100, -100, 0], 425 | a2b: [30, -120, 0], 426 | ab2: [20, -90, 0], 427 | b3: [0, -30, 0], 428 | b2y: [0, 30, -60], 429 | by2: [0, 60, -60], 430 | y3: [0, 200, 0], 431 | a2y: [230, -50, 0], 432 | ay2: [200, 30, 0], 433 | aby: [30, 30, -100] 434 | }, 9) 435 | .setBezierTriangleFill('#ffa69e') 436 | .setBezierTriangleStroke('#ff798b') 437 | .setBezierTriangleStrokeWidth(0.1) 438 | .action() 439 | 440 | } 441 | 442 | componentDidMount () { 443 | 444 | this.drawChromatic3D() 445 | 446 | } 447 | 448 | render () { 449 | return ( 450 |
457 |
463 | { 465 | this.setState({ 466 | selectedKey: e.key 467 | }) 468 | 469 | clearInterval(this.globalInt); 470 | 471 | if (e.key == 'chromatic-3d') { 472 | this.drawChromatic3D.call(this); 473 | } 474 | 475 | if (e.key == 'bezier-surface') { 476 | this.drawBezierSurface.call(this); 477 | } 478 | 479 | if (e.key == 'cube') { 480 | this.drawCube.call(this); 481 | } 482 | 483 | if (e.key == 'bezier-animation') { 484 | this.drawBezierSurfaceAnimation.call(this); 485 | } 486 | 487 | if (e.key == 'bezier-curve') { 488 | this.drawBezierCurve.call(this); 489 | } 490 | 491 | if (e.key == 'line') { 492 | this.drawLine.call(this); 493 | } 494 | 495 | if (e.key == 'sphere') { 496 | this.drawSphere.call(this); 497 | } 498 | }} 499 | selectedKeys={this.state.selectedKey} 500 | mode="horizontal" 501 | theme="dark" 502 | > 503 | 504 | Chromatic 3D 505 | 506 | 507 | Bezier Surface 508 | 509 | 510 | Bezier Surface Animation 511 | 512 | 513 | Cube 514 | 515 | 516 | Sphere 517 | 518 | 519 | Bezier Curve 520 | 521 | 522 | Line 523 | 524 | 525 | 526 | github 527 | 528 | 529 | 530 |
531 |
537 | 538 | 539 |
540 | 541 |
{ 545 | if (this.state.upActive) { 546 | return 33 547 | } else if (this.state.downActive) { 548 | return 27; 549 | } else { 550 | return 30 551 | } 552 | })(), 553 | right: (() => { 554 | if (this.state.leftActive) { 555 | return 33 556 | } else if (this.state.rightActive) { 557 | return 27; 558 | } else { 559 | return 30 560 | } 561 | })(), 562 | width: 100, 563 | height: 100, 564 | borderRadius: 50, 565 | backgroundColor: 'rgba(101, 138, 215, 0.1)', 566 | boxShadow: '2px 2px 2px #108ee9' 567 | }} 568 | className="control-panel" 569 | > 570 |
{ 583 | this.state.upInt = setInterval(() => { 584 | this.up.call(this) 585 | }, 100); 586 | this.setState({ 587 | upActive: true 588 | }) 589 | }} 590 | onMouseUp={()=> { 591 | setTimeout(() => { 592 | clearInterval(this.state.upInt) 593 | this.setState({ 594 | upActive: false 595 | }) 596 | }, 50) 597 | }} 598 | onMouseLeave={()=>{ 599 | setTimeout(() => { 600 | clearInterval(this.state.upInt) 601 | this.setState({ 602 | upActive: false 603 | }) 604 | }, 50) 605 | }} 606 | onTouchStart={()=>{ 607 | this.state.upInt = setInterval(() => { 608 | this.up.call(this) 609 | this.setState({ 610 | upActive: true 611 | }) 612 | }, 100); 613 | }} 614 | onTouchEnd={()=>{ 615 | setTimeout(() => { 616 | clearInterval(this.state.upInt) 617 | this.setState({ 618 | upActive: false 619 | }) 620 | }, 50) 621 | }} 622 | /> 623 |
{ 636 | this.state.leftInt = setInterval(() => { 637 | this.left.call(this) 638 | }, 100); 639 | this.setState({ 640 | leftActive: true 641 | }) 642 | }} 643 | onMouseUp={()=> { 644 | setTimeout(() => { 645 | clearInterval(this.state.leftInt) 646 | this.setState({ 647 | leftActive: false 648 | }) 649 | }, 50) 650 | }} 651 | onMouseLeave={()=>{ 652 | setTimeout(() => { 653 | clearInterval(this.state.leftInt) 654 | this.setState({ 655 | leftActive: false 656 | }) 657 | }, 50) 658 | }} 659 | onTouchStart={()=>{ 660 | this.state.leftInt = setInterval(() => { 661 | this.left.call(this) 662 | this.setState({ 663 | leftActive: true 664 | }) 665 | }, 100); 666 | }} 667 | onTouchEnd={()=>{ 668 | setTimeout(() => { 669 | clearInterval(this.state.leftInt) 670 | this.setState({ 671 | leftActive: false 672 | }) 673 | }, 50) 674 | }} 675 | /> 676 |
{ 689 | this.state.rightInt = setInterval(() => { 690 | this.right.call(this) 691 | }, 100); 692 | this.setState({ 693 | rightActive: true 694 | }) 695 | }} 696 | onMouseUp={()=> { 697 | setTimeout(() => { 698 | clearInterval(this.state.rightInt) 699 | this.setState({ 700 | rightActive: false 701 | }) 702 | }, 50) 703 | }} 704 | onMouseLeave={()=>{ 705 | setTimeout(() => { 706 | clearInterval(this.state.rightInt) 707 | this.setState({ 708 | rightActive: false 709 | }) 710 | }, 50) 711 | }} 712 | onTouchStart={()=>{ 713 | this.state.rightInt = setInterval(() => { 714 | this.right.call(this) 715 | this.setState({ 716 | rightActive: true 717 | }) 718 | }, 100); 719 | }} 720 | onTouchEnd={()=>{ 721 | setTimeout(() => { 722 | clearInterval(this.state.rightInt) 723 | this.setState({ 724 | rightActive: false 725 | }) 726 | }, 50) 727 | }} 728 | /> 729 |
{ 742 | this.state.downInt = setInterval(() => { 743 | this.down.call(this) 744 | }, 100); 745 | this.setState({ 746 | downActive: true 747 | }) 748 | }} 749 | onMouseUp={()=> { 750 | setTimeout(() => { 751 | clearInterval(this.state.downInt) 752 | this.setState({ 753 | downActive: false 754 | }) 755 | }, 50) 756 | }} 757 | onMouseLeave={()=>{ 758 | setTimeout(() => { 759 | clearInterval(this.state.downInt) 760 | this.setState({ 761 | downActive: false 762 | }) 763 | }, 50) 764 | }} 765 | onTouchStart={()=>{ 766 | this.state.downInt = setInterval(() => { 767 | this.down.call(this) 768 | this.setState({ 769 | downActive: true 770 | }) 771 | }, 100); 772 | }} 773 | onTouchEnd={()=>{ 774 | setTimeout(() => { 775 | clearInterval(this.state.downInt) 776 | this.setState({ 777 | downActive: false 778 | }) 779 | }, 50) 780 | }} 781 | /> 782 |
783 | 784 |
785 | ) 786 | } 787 | } 788 | 789 | 790 | ReactDOM.render(, document.getElementById('app')); -------------------------------------------------------------------------------- /demo/exhibition/left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/captainwz/svg-3d-builder/336c8094f2f024b55b05ad8a95e3de5e1ae8247b/demo/exhibition/left.png -------------------------------------------------------------------------------- /demo/exhibition/right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/captainwz/svg-3d-builder/336c8094f2f024b55b05ad8a95e3de5e1ae8247b/demo/exhibition/right.png -------------------------------------------------------------------------------- /demo/exhibition/tweed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/captainwz/svg-3d-builder/336c8094f2f024b55b05ad8a95e3de5e1ae8247b/demo/exhibition/tweed.png -------------------------------------------------------------------------------- /demo/exhibition/up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/captainwz/svg-3d-builder/336c8094f2f024b55b05ad8a95e3de5e1ae8247b/demo/exhibition/up.png -------------------------------------------------------------------------------- /demo/exhibition/webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | 3 | module.exports = { 4 | entry: path.resolve(__dirname, 'index.js'), 5 | output: { 6 | path: path.resolve(__dirname), 7 | filename: 'bundle.js' 8 | }, 9 | resolve: { 10 | extensions: ['.js'] 11 | }, 12 | module: { 13 | rules: [ 14 | { 15 | test: /\.js$/, 16 | exclude: [/node_modules/], 17 | loader: 'babel-loader', 18 | options: { 19 | presets: ['react', 'stage-0', 'env'] 20 | }, 21 | } 22 | ] 23 | }, 24 | mode: 'production' 25 | } -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 8 | 9 | var _d3 = require('d3'); 10 | 11 | var d3 = _interopRequireWildcard(_d3); 12 | 13 | var _util = require('./util'); 14 | 15 | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } 16 | 17 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 18 | 19 | var Builder = function () { 20 | function Builder() { 21 | _classCallCheck(this, Builder); 22 | 23 | Object.assign(this, { 24 | svg: null, 25 | selector: null, 26 | bezierSurfaceGroup: null, 27 | lineList: [], 28 | lineIndex: null, 29 | bezierCurveList: [], 30 | bezierCurveIndex: null, 31 | bezierSurfaceList: [], 32 | bezierSurfaceIndex: null, 33 | bezierTriangleList: [], 34 | bezierTriangleIndex: null, 35 | camera: { 36 | anchor: [0, 0, 0], 37 | d: 300, 38 | alpha: 0, 39 | beta: 0 40 | }, 41 | screen: { 42 | ratio: 0.5, 43 | offsetX: 0, 44 | offsetY: 0 45 | }, 46 | axis: { 47 | show: false, 48 | xLength: 200, 49 | xColor: '#000', 50 | yLength: 200, 51 | yColor: '#000', 52 | zLength: 200, 53 | zColor: '#000', 54 | xRef: null, 55 | yRef: null, 56 | zRef: null 57 | } 58 | }); 59 | } 60 | 61 | _createClass(Builder, [{ 62 | key: 'reset', 63 | value: function reset() { 64 | 65 | if (this.selector) { 66 | this.selector.remove(); 67 | } 68 | 69 | Object.assign(this, { 70 | svg: null, 71 | selector: null, 72 | bezierSurfaceGroup: null, 73 | lineList: [], 74 | lineIndex: null, 75 | bezierCurveList: [], 76 | bezierCurveIndex: null, 77 | bezierSurfaceList: [], 78 | bezierSurfaceIndex: null, 79 | bezierTriangleList: [], 80 | bezierTriangleIndex: null, 81 | camera: { 82 | anchor: [0, 0, 0], 83 | d: 300, 84 | alpha: 0, 85 | beta: 0 86 | }, 87 | screen: { 88 | ratio: 0.5, 89 | offsetX: 0, 90 | offsetY: 0 91 | }, 92 | axis: { 93 | show: false, 94 | xLength: 200, 95 | xColor: '#000', 96 | yLength: 200, 97 | yColor: '#000', 98 | zLength: 200, 99 | zColor: '#000', 100 | xRef: null, 101 | yRef: null, 102 | zRef: null 103 | } 104 | }); 105 | 106 | return this; 107 | } 108 | }, { 109 | key: 'setCamera', 110 | value: function setCamera(opt) { 111 | 112 | if (!opt) { 113 | opt = {}; 114 | } 115 | 116 | var _opt = opt, 117 | _opt$anchor = _opt.anchor, 118 | anchor = _opt$anchor === undefined ? this.camera.anchor : _opt$anchor, 119 | _opt$d = _opt.d, 120 | d = _opt$d === undefined ? this.camera.d : _opt$d, 121 | _opt$alpha = _opt.alpha, 122 | alpha = _opt$alpha === undefined ? this.camera.alpha : _opt$alpha, 123 | _opt$beta = _opt.beta, 124 | beta = _opt$beta === undefined ? this.camera.beta : _opt$beta; 125 | 126 | 127 | if (!anchor instanceof Array || anchor.length != 3) { 128 | throw new Error(); 129 | } 130 | 131 | this.camera = { 132 | anchor: anchor, 133 | d: d, 134 | alpha: alpha, 135 | beta: beta 136 | }; 137 | 138 | return this; 139 | } 140 | }, { 141 | key: 'setScreen', 142 | value: function setScreen(opt) { 143 | 144 | if (!opt) { 145 | opt = {}; 146 | } 147 | 148 | var _opt2 = opt, 149 | _opt2$ratio = _opt2.ratio, 150 | ratio = _opt2$ratio === undefined ? this.screen.ratio : _opt2$ratio, 151 | _opt2$offsetX = _opt2.offsetX, 152 | offsetX = _opt2$offsetX === undefined ? this.screen.offsetX : _opt2$offsetX, 153 | _opt2$offsetY = _opt2.offsetY, 154 | offsetY = _opt2$offsetY === undefined ? this.screen.offsetY : _opt2$offsetY; 155 | 156 | 157 | this.screen = { 158 | ratio: ratio, 159 | offsetX: offsetX, 160 | offsetY: offsetY 161 | }; 162 | 163 | this.selector.attr('transform', 'translate(' + offsetX + ', ' + offsetY + ')'); 164 | 165 | return this; 166 | } 167 | }, { 168 | key: 'setAxis', 169 | value: function setAxis(opt) { 170 | 171 | if (!opt) { 172 | opt = {}; 173 | } 174 | 175 | var _opt3 = opt, 176 | _opt3$show = _opt3.show, 177 | show = _opt3$show === undefined ? this.axis.show : _opt3$show, 178 | _opt3$xLength = _opt3.xLength, 179 | xLength = _opt3$xLength === undefined ? this.axis.xLength : _opt3$xLength, 180 | _opt3$xColor = _opt3.xColor, 181 | xColor = _opt3$xColor === undefined ? this.axis.xColor : _opt3$xColor, 182 | _opt3$yLength = _opt3.yLength, 183 | yLength = _opt3$yLength === undefined ? this.axis.yLength : _opt3$yLength, 184 | _opt3$yColor = _opt3.yColor, 185 | yColor = _opt3$yColor === undefined ? this.axis.yColor : _opt3$yColor, 186 | _opt3$zLength = _opt3.zLength, 187 | zLength = _opt3$zLength === undefined ? this.axis.zLength : _opt3$zLength, 188 | _opt3$zColor = _opt3.zColor, 189 | zColor = _opt3$zColor === undefined ? this.axis.zColor : _opt3$zColor; 190 | 191 | 192 | this.axis = { 193 | show: show, 194 | xLength: xLength, 195 | xColor: xColor, 196 | yLength: yLength, 197 | yColor: yColor, 198 | zLength: zLength, 199 | zColor: zColor, 200 | xRef: this.axis.xRef ? this.axis.xRef : this.selector.append('path'), 201 | yRef: this.axis.yRef ? this.axis.yRef : this.selector.append('path'), 202 | zRef: this.axis.zRef ? this.axis.zRef : this.selector.append('path') 203 | }; 204 | 205 | return this; 206 | } 207 | }, { 208 | key: 'select', 209 | value: function select(selector) { 210 | this.svg = d3.select(selector); 211 | this.selector = this.svg.append('g'); 212 | this.bezierSurfaceGroup = this.selector.append('g'); 213 | 214 | return this; 215 | } 216 | }, { 217 | key: 'drawLine', 218 | value: function drawLine(d) { 219 | 220 | this.lineList.push({ 221 | ref: this.selector.append('path'), 222 | originPath: d, 223 | stroke: '#000', 224 | fill: 'transparent' 225 | }); 226 | 227 | this.lineIndex = this.lineList.length - 1; 228 | 229 | return this; 230 | } 231 | }, { 232 | key: 'redrawLine', 233 | value: function redrawLine(d, index) { 234 | if (index == undefined) { 235 | index = this.lineIndex; 236 | } 237 | 238 | this.lineList[index].originPath = d; 239 | 240 | return this; 241 | } 242 | }, { 243 | key: 'drawBezierCurve', 244 | value: function drawBezierCurve(p0, p1, p2, p3, n) { 245 | this.bezierCurveList.push({ 246 | ref: this.selector.append('path'), 247 | p0: p0, 248 | p1: p1, 249 | p2: p2, 250 | p3: p3, 251 | n: n, 252 | stroke: '#000', 253 | fill: 'transparent' 254 | }); 255 | 256 | this.bezierCurveIndex = this.bezierCurveList.length - 1; 257 | 258 | return this; 259 | } 260 | }, { 261 | key: 'redrawBezierCurve', 262 | value: function redrawBezierCurve(p0, p1, p2, p3, n, index) { 263 | if (index == undefined) { 264 | index = this.bezierCurveIndex; 265 | } 266 | 267 | this.bezierCurveList[index].p0 = p0; 268 | this.bezierCurveList[index].p1 = p1; 269 | this.bezierCurveList[index].p2 = p2; 270 | this.bezierCurveList[index].p3 = p3; 271 | this.bezierCurveList[index].n = n; 272 | 273 | return this; 274 | } 275 | }, { 276 | key: 'drawBezierSurface', 277 | value: function drawBezierSurface(matrix, density) { 278 | this.bezierSurfaceList.push({ 279 | ref: this.selector.append('path'), 280 | matrix: matrix, 281 | density: density, 282 | stroke: '#000', 283 | strokeWidth: 0.5, 284 | fill: 'transparent' 285 | }); 286 | 287 | this.bezierSurfaceIndex = this.bezierSurfaceList.length - 1; 288 | 289 | return this; 290 | } 291 | }, { 292 | key: 'redrawBezierSurface', 293 | value: function redrawBezierSurface(matrix, density, index) { 294 | if (index == undefined) { 295 | index = this.bezierSurfaceIndex; 296 | } 297 | 298 | this.bezierSurfaceList[index].matrix = matrix; 299 | this.bezierSurfaceList[index].density = density; 300 | 301 | return this; 302 | } 303 | }, { 304 | key: 'drawBezierTriangle', 305 | value: function drawBezierTriangle(control, density) { 306 | 307 | this.bezierTriangleList.push({ 308 | control: control, 309 | density: density, 310 | stroke: '#000', 311 | strokeWidth: 0.5, 312 | fill: 'transparent' 313 | }); 314 | 315 | this.bezierTriangleIndex = this.bezierTriangleList.length - 1; 316 | return this; 317 | } 318 | }, { 319 | key: 'redrawBezierTriangle', 320 | value: function redrawBezierTriangle(control, density, index) { 321 | if (index == undefined) { 322 | index = this.bezierTriangleIndex; 323 | } 324 | 325 | this.bezierTriangleList[index].control = control; 326 | this.bezierTriangleList[index].density = density; 327 | 328 | return this; 329 | } 330 | }, { 331 | key: 'setLineStroke', 332 | value: function setLineStroke(color, index) { 333 | 334 | if (index == undefined) { 335 | index = this.lineIndex; 336 | } 337 | 338 | this.lineList[index].stroke = color; 339 | return this; 340 | } 341 | }, { 342 | key: 'setBezierCurveStroke', 343 | value: function setBezierCurveStroke(color, index) { 344 | if (index == undefined) { 345 | index = this.bezierCurveIndex; 346 | } 347 | 348 | this.bezierCurveList[index].stroke = color; 349 | return this; 350 | } 351 | }, { 352 | key: 'setBezierSurfaceStroke', 353 | value: function setBezierSurfaceStroke(color, index) { 354 | if (index == undefined) { 355 | index = this.bezierSurfaceIndex; 356 | } 357 | 358 | this.bezierSurfaceList[index].stroke = color; 359 | return this; 360 | } 361 | }, { 362 | key: 'setBezierTriangleStroke', 363 | value: function setBezierTriangleStroke(color, index) { 364 | if (index == undefined) { 365 | index = this.bezierTriangleIndex; 366 | } 367 | 368 | this.bezierTriangleList[index].stroke = color; 369 | return this; 370 | } 371 | }, { 372 | key: 'setBezierSurfaceStrokeWidth', 373 | value: function setBezierSurfaceStrokeWidth(width, index) { 374 | if (index == undefined) { 375 | index = this.bezierSurfaceIndex; 376 | } 377 | 378 | this.bezierSurfaceList[index].strokeWidth = width; 379 | return this; 380 | } 381 | }, { 382 | key: 'setBezierTriangleStrokeWidth', 383 | value: function setBezierTriangleStrokeWidth(width, index) { 384 | if (index == undefined) { 385 | index = this.bezierTriangleIndex; 386 | } 387 | this.bezierTriangleList[index].strokeWidth = width; 388 | return this; 389 | } 390 | }, { 391 | key: 'setBezierSurfaceFill', 392 | value: function setBezierSurfaceFill(color, index) { 393 | if (index == undefined) { 394 | index = this.bezierSurfaceIndex; 395 | } 396 | 397 | this.bezierSurfaceList[index].fill = color; 398 | return this; 399 | } 400 | }, { 401 | key: 'setBezierTriangleFill', 402 | value: function setBezierTriangleFill(color, index) { 403 | if (index == undefined) { 404 | index = this.bezierTriangleIndex; 405 | } 406 | this.bezierTriangleList[index].fill = color; 407 | return this; 408 | } 409 | }, { 410 | key: 'action', 411 | value: function action() { 412 | var _this = this; 413 | 414 | var camera = this.camera; 415 | var screen = this.screen; 416 | 417 | if (this.axis.show) { 418 | 419 | if (this.axis.xRef) { 420 | var d = 'M 0 0 0 L ' + this.axis.xLength + ' 0 0 M ' + this.axis.xLength + ' 0 0 l -5 5 0 M ' + this.axis.xLength + ' 0 0 l -5 -5 0'; 421 | this.axis.xRef.transition().attr('d', (0, _util.getLinePath)(d, camera.anchor, camera.d, camera.alpha, camera.beta, screen.ratio)).attr('stroke', this.axis.xColor).attr('fill', 'transparent'); 422 | } 423 | 424 | if (this.axis.yRef) { 425 | var _d = 'M 0 0 0 L 0 ' + this.axis.yLength + ' 0 M 0 ' + this.axis.yLength + ' 0 l 5 -5 0 M 0 ' + this.axis.yLength + ' 0 l -5 -5 0'; 426 | this.axis.yRef.transition().attr('d', (0, _util.getLinePath)(_d, camera.anchor, camera.d, camera.alpha, camera.beta, screen.ratio)).attr('stroke', this.axis.yColor).attr('fill', 'transparent'); 427 | } 428 | 429 | if (this.axis.zRef) { 430 | var _d2 = 'M 0 0 0 L 0 0 ' + this.axis.zLength + ' M 0 0 ' + this.axis.zLength + ' l 5 0 -5 M 0 0 ' + this.axis.zLength + ' l -5 0 -5'; 431 | this.axis.zRef.transition().attr('d', (0, _util.getLinePath)(_d2, camera.anchor, camera.d, camera.alpha, camera.beta, screen.ratio)).attr('stroke', this.axis.zColor).attr('fill', 'transparent'); 432 | } 433 | } 434 | 435 | this.lineList.forEach(function (path, k) { 436 | 437 | path.ref.transition().attr('d', (0, _util.getLinePath)(path.originPath, camera.anchor, camera.d, camera.alpha, camera.beta, screen.ratio)).attr('stroke', path.stroke).attr('fill', path.fill); 438 | }); 439 | 440 | this.bezierCurveList.forEach(function (path, k) { 441 | 442 | path.ref.transition().attr('d', (0, _util.getBezierCurvePath)(path.p0, path.p1, path.p2, path.p3, path.n, camera.anchor, camera.d, camera.alpha, camera.beta, screen.ratio)).attr('stroke', path.stroke).attr('fill', path.fill); 443 | }); 444 | 445 | this.bezierSurfaceGroup.selectAll('*').remove(); 446 | 447 | var arr = []; 448 | 449 | this.bezierSurfaceList.forEach(function (path, k) { 450 | 451 | var a = (0, _util.getBezierSurfacePath)(path.matrix, path.density, camera.anchor, camera.d, camera.alpha, camera.beta, screen.ratio); 452 | 453 | a = a.map(function (item) { 454 | return Object.assign(item, { 455 | stroke: path.stroke, 456 | fill: path.fill, 457 | strokeWidth: path.strokeWidth 458 | }); 459 | }); 460 | 461 | arr = arr.concat(a); 462 | 463 | /* 464 | path 465 | .ref 466 | .transition() 467 | .attr('d', getBezierSurfacePath(path.matrix, path.density, camera.anchor, camera.d, camera.alpha, camera.beta, screen.ratio)) 468 | .attr('stroke', path.stroke) 469 | .attr('fill', path.fill)*/ 470 | }); 471 | 472 | this.bezierTriangleList.forEach(function (triangle, k) { 473 | var a = (0, _util.getBezierTrianglePath)(triangle.control, triangle.density, camera.anchor, camera.d, camera.alpha, camera.beta, screen.ratio); 474 | 475 | a = a.map(function (item) { 476 | return Object.assign(item, { 477 | stroke: triangle.stroke, 478 | fill: triangle.fill, 479 | strokeWidth: triangle.strokeWidth 480 | }); 481 | }); 482 | 483 | arr = arr.concat(a); 484 | }); 485 | 486 | var cameraPoint = (0, _util.getCameraPoint)(camera.anchor, camera.d, camera.alpha, camera.beta); 487 | 488 | arr.sort(function (aa, bb) { 489 | var a = aa.center; 490 | var b = bb.center; 491 | 492 | var x = cameraPoint[0]; 493 | var y = cameraPoint[1]; 494 | var z = cameraPoint[2]; 495 | var adx = (a[0] - x) * (a[0] - x); 496 | var ady = (a[1] - y) * (a[1] - y); 497 | var adz = (a[2] - z) * (a[2] - z); 498 | var bdx = (b[0] - x) * (b[0] - x); 499 | var bdy = (b[1] - y) * (b[1] - y); 500 | var bdz = (b[2] - z) * (b[2] - z); 501 | 502 | return adx + ady + adz < bdx + bdy + bdz; 503 | }).forEach(function (item) { 504 | _this.bezierSurfaceGroup.append('path').attr('d', item.d).attr('stroke', item.stroke).attr('stroke-width', item.strokeWidth).attr('fill', item.fill); 505 | }); 506 | 507 | return this; 508 | } 509 | }]); 510 | 511 | return Builder; 512 | }(); 513 | 514 | exports.default = new Builder(); -------------------------------------------------------------------------------- /lib/util.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.getCameraPoint = getCameraPoint; 7 | exports.getProjection = getProjection; 8 | exports.getLinePath = getLinePath; 9 | exports.getBezierCurvePath = getBezierCurvePath; 10 | exports.getBezierSurfacePath = getBezierSurfacePath; 11 | exports.getBezierTrianglePath = getBezierTrianglePath; 12 | function getCameraPoint(anchor, d, alpha, beta) { 13 | var ax = anchor[0]; // anchor x 14 | var ay = anchor[1]; // anchor y 15 | var az = anchor[2]; // anchor z 16 | 17 | var dx = d * Math.cos(beta) * Math.sin(alpha); 18 | var dy = d * Math.sin(beta); 19 | var dz = d * Math.cos(alpha) * Math.cos(beta); 20 | 21 | return [ax + dx, ay + dy, az + dz]; 22 | } 23 | 24 | function getProjection(anchor, d, alpha, beta, ratio, origin) { 25 | var ax = anchor[0]; // anchor x 26 | var ay = anchor[1]; // anchor y 27 | var az = anchor[2]; // anchor z 28 | 29 | var ox = origin[0]; // origin x 30 | var oy = origin[1]; // origin y 31 | var oz = origin[2]; // origin z 32 | 33 | var dx = d * Math.cos(beta) * Math.sin(alpha); 34 | var dy = d * Math.sin(beta); 35 | // let dy = d*Math.cos(alpha)*Math.sin(beta); 36 | var dz = d * Math.cos(alpha) * Math.cos(beta); 37 | 38 | var mod = Math.sqrt(dx * dx + dy * dy + dz * dz); 39 | var nv = [-dx / mod, -dy / mod, -dz / mod]; // normal vector 40 | 41 | var so = [ax + (1 - ratio) * dx, ay + (1 - ratio) * dy, az + (1 - ratio) * dz]; // screen o point 42 | 43 | 44 | var c0 = function c0(thita) { 45 | if (Math.cos(thita) == 0) { 46 | return 1; 47 | } else { 48 | return Math.cos(thita) / Math.abs(Math.cos(thita)) > 0 ? 1 : -1; 49 | } 50 | }; 51 | 52 | var c1 = function c1(thita) { 53 | if (Math.sin(thita) == 0) { 54 | return 1; 55 | } else { 56 | return Math.sin(thita) / Math.abs(Math.sin(thita)) > 0 ? 1 : -1; 57 | } 58 | }; 59 | // coefficient 60 | 61 | // let sy = [0, 62 | // Math.cos(beta)/Math.sqrt(Math.cos(beta)*Math.cos(beta) + Math.sin(beta)*Math.sin(beta)), 63 | // -Math.sin(beta)/Math.sqrt(Math.cos(beta)*Math.cos(beta) + Math.sin(beta)*Math.sin(beta)) 64 | // ] 65 | 66 | // let f = (dy*dy + dz*dz); 67 | // let m = Math.sqrt(f*f + dy*dy*dx*dx + dz*dz*dx*dx); 68 | // let sx = [c0(alpha)*f/m, -1*c0(alpha)*dy*dx/m, -1*c0(alpha)*dz*dx/m]; 69 | // screen x axis 70 | 71 | var syy = [-1 * Math.sin(beta) * Math.sin(alpha), Math.cos(beta), -1 * Math.sin(beta) * Math.cos(alpha)]; 72 | 73 | var yf = Math.sqrt(syy[0] * syy[0] + syy[1] * syy[1] + syy[2] * syy[2]); 74 | var sy = [syy[0] / yf, syy[1] / yf, syy[2] / yf]; 75 | 76 | var sx = [Math.cos(alpha), 0, -Math.sin(alpha)]; 77 | 78 | var ray = [ox - ax - dx, oy - ay - dy, oz - az - dz]; 79 | var p = d * ratio / (ray[0] * nv[0] + ray[1] * nv[1] + ray[2] * nv[2]); 80 | var focus = [ax + dx + ray[0] * p, ay + dy + ray[1] * p, az + dz + ray[2] * p]; 81 | var rf = [focus[0] - so[0], focus[1] - so[1], focus[2] - so[2]]; // relative focus 82 | var rx = rf[0] * sx[0] + rf[1] * sx[1] + rf[2] * sx[2]; 83 | var ry = rf[0] * sy[0] + rf[1] * sy[1] + rf[2] * sy[2]; 84 | 85 | return { 86 | x: rx, 87 | y: ry 88 | }; 89 | } 90 | 91 | function getLinePath(path, anchor, d, alpha, beta, ratio) { 92 | var arr = path.replace(/\,/g, ' ').replace(/[Mm]/g, function (r) { 93 | return r + ' '; 94 | }).replace(/[LlCcSsQqTt]/g, function (r) { 95 | return ' ' + r + ' '; 96 | }).replace(/[Zz]/g, function (r) { 97 | return ' ' + r; 98 | }).replace(/\s+/g, ',').split(','); 99 | 100 | var newArr = []; 101 | 102 | var f = function f(origin) { 103 | return getProjection(anchor, d, alpha, beta, ratio, origin); 104 | }; 105 | 106 | arr = arr.map(function (item) { 107 | if (/\d/.test(item)) { 108 | return Number(item); 109 | } else { 110 | return item; 111 | } 112 | }); 113 | 114 | arr.forEach(function (item, k) { 115 | if (item == 'm' || item == 'M') { 116 | newArr.push('M'); 117 | var r = f([arr[k + 1], arr[k + 2], arr[k + 3]]); 118 | newArr.push(r.x); 119 | newArr.push(r.y); 120 | } 121 | 122 | if (item == 'L') { 123 | newArr.push('L'); 124 | var _r = f([arr[k + 1], arr[k + 2], arr[k + 3]]); 125 | newArr.push(_r.x); 126 | newArr.push(_r.y); 127 | } 128 | 129 | if (item == 'l') { 130 | newArr.push('L'); 131 | arr[k + 1] = arr[k - 3] + arr[k + 1]; 132 | arr[k + 2] = arr[k - 2] + arr[k + 2]; 133 | arr[k + 3] = arr[k - 1] + arr[k + 3]; 134 | var _r2 = f([arr[k + 1], arr[k + 2], arr[k + 3]]); 135 | newArr.push(_r2.x); 136 | newArr.push(_r2.y); 137 | } 138 | 139 | if (item == 'C') { 140 | newArr.push('C'); 141 | 142 | var _r3 = f([arr[k + 1], arr[k + 2], arr[k + 3]]); 143 | newArr.push(_r3.x); 144 | newArr.push(_r3.y); 145 | 146 | _r3 = f([arr[k + 4], arr[k + 5], arr[k + 6]]); 147 | newArr.push(_r3.x); 148 | newArr.push(_r3.y); 149 | 150 | _r3 = f([arr[k + 7], arr[k + 8], arr[k + 9]]); 151 | newArr.push(_r3.x); 152 | newArr.push(_r3.y); 153 | } 154 | 155 | if (item == 'c') { 156 | newArr.push('C'); 157 | 158 | arr[k + 1] = arr[k - 3] + arr[k + 1]; 159 | arr[k + 2] = arr[k - 2] + arr[k + 2]; 160 | arr[k + 3] = arr[k - 1] + arr[k + 3]; 161 | var _r4 = f([arr[k + 1], arr[k + 2], arr[k + 3]]); 162 | newArr.push(_r4.x); 163 | newArr.push(_r4.y); 164 | 165 | arr[k + 4] = arr[k - 3] + arr[k + 4]; 166 | arr[k + 5] = arr[k - 2] + arr[k + 5]; 167 | arr[k + 6] = arr[k - 1] + arr[k + 6]; 168 | _r4 = f([arr[k + 4], arr[k + 5], arr[k + 6]]); 169 | newArr.push(_r4.x); 170 | newArr.push(_r4.y); 171 | 172 | arr[k + 7] = arr[k - 3] + arr[k + 7]; 173 | arr[k + 8] = arr[k - 2] + arr[k + 8]; 174 | arr[k + 9] = arr[k - 1] + arr[k + 9]; 175 | _r4 = f([arr[k + 7], arr[k + 8], arr[k + 9]]); 176 | newArr.push(_r4.x); 177 | newArr.push(_r4.y); 178 | } 179 | 180 | if (item == 'S') { 181 | newArr.push('S'); 182 | var _r5 = f([arr[k + 1], arr[k + 2], arr[k + 3]]); 183 | newArr.push(_r5.x); 184 | newArr.push(_r5.y); 185 | 186 | _r5 = f([arr[k + 4], arr[k + 5], arr[k + 6]]); 187 | newArr.push(_r5.x); 188 | newArr.push(_r5.y); 189 | } 190 | 191 | if (item == 's') { 192 | newArr.push('S'); 193 | 194 | arr[k + 1] = arr[k - 3] + arr[k + 1]; 195 | arr[k + 2] = arr[k - 2] + arr[k + 2]; 196 | arr[k + 3] = arr[k - 1] + arr[k + 3]; 197 | var _r6 = f([arr[k + 1], arr[k + 2], arr[k + 3]]); 198 | newArr.push(_r6.x); 199 | newArr.push(_r6.y); 200 | 201 | arr[k + 4] = arr[k - 3] + arr[k + 4]; 202 | arr[k + 5] = arr[k - 2] + arr[k + 5]; 203 | arr[k + 6] = arr[k - 1] + arr[k + 6]; 204 | _r6 = f([arr[k + 4], arr[k + 5], arr[k + 6]]); 205 | newArr.push(_r6.x); 206 | newArr.push(_r6.y); 207 | } 208 | 209 | if (item == 'Q') { 210 | newArr.push('Q'); 211 | var _r7 = f([arr[k + 1], arr[k + 2], arr[k + 3]]); 212 | newArr.push(_r7.x); 213 | newArr.push(_r7.y); 214 | 215 | _r7 = f([arr[k + 4], arr[k + 5], arr[k + 6]]); 216 | newArr.push(_r7.x); 217 | newArr.push(_r7.y); 218 | } 219 | 220 | if (item == 'q') { 221 | newArr.push('Q'); 222 | 223 | arr[k + 1] = arr[k - 3] + arr[k + 1]; 224 | arr[k + 2] = arr[k - 2] + arr[k + 2]; 225 | arr[k + 3] = arr[k - 1] + arr[k + 3]; 226 | var _r8 = f([arr[k + 1], arr[k + 2], arr[k + 3]]); 227 | newArr.push(_r8.x); 228 | newArr.push(_r8.y); 229 | 230 | arr[k + 4] = arr[k - 3] + arr[k + 4]; 231 | arr[k + 5] = arr[k - 2] + arr[k + 5]; 232 | arr[k + 6] = arr[k - 1] + arr[k + 6]; 233 | _r8 = f([arr[k + 4], arr[k + 5], arr[k + 6]]); 234 | newArr.push(_r8.x); 235 | newArr.push(_r8.y); 236 | } 237 | 238 | if (item == 'T') { 239 | newArr.push('T'); 240 | var _r9 = f([arr[k + 1], arr[k + 2], arr[k + 3]]); 241 | newArr.push(_r9.x); 242 | newArr.push(_r9.y); 243 | } 244 | 245 | if (item == 't') { 246 | newArr.push('T'); 247 | arr[k + 1] = arr[k - 3] + arr[k + 1]; 248 | arr[k + 2] = arr[k - 2] + arr[k + 2]; 249 | arr[k + 3] = arr[k - 1] + arr[k + 3]; 250 | var _r10 = f([arr[k + 1], arr[k + 2], arr[k + 3]]); 251 | newArr.push(_r10.x); 252 | newArr.push(_r10.y); 253 | } 254 | 255 | if (item == 'z' || item == 'Z') { 256 | newArr.push('Z'); 257 | } 258 | }); 259 | 260 | return newArr.join(' '); 261 | } 262 | 263 | var getBezierCurveVectorArr = function getBezierCurveVectorArr(p0, p1, p2, p3, n) { 264 | 265 | var x0 = p0[0]; 266 | var y0 = p0[1]; 267 | var z0 = p0[2]; 268 | 269 | var x1 = p1[0]; 270 | var y1 = p1[1]; 271 | var z1 = p1[2]; 272 | 273 | var x2 = p2[0]; 274 | var y2 = p2[1]; 275 | var z2 = p2[2]; 276 | 277 | var x3 = p3[0]; 278 | var y3 = p3[1]; 279 | var z3 = p3[2]; 280 | 281 | var arr = []; 282 | 283 | var k = 0; 284 | 285 | for (; k <= n; k++) { 286 | var t = k / n; 287 | 288 | var bx = (1 - t) * (1 - t) * (1 - t) * x0 + 3 * t * (1 - t) * (1 - t) * x1 + 3 * t * t * (1 - t) * x2 + t * t * t * x3; 289 | var by = (1 - t) * (1 - t) * (1 - t) * y0 + 3 * t * (1 - t) * (1 - t) * y1 + 3 * t * t * (1 - t) * y2 + t * t * t * y3; 290 | var bz = (1 - t) * (1 - t) * (1 - t) * z0 + 3 * t * (1 - t) * (1 - t) * z1 + 3 * t * t * (1 - t) * z2 + t * t * t * z3; 291 | 292 | arr.push({ 293 | x: bx, 294 | y: by, 295 | z: bz 296 | }); 297 | } 298 | return arr; 299 | }; 300 | 301 | function getBezierCurvePath(p0, p1, p2, p3, n, anchor, d, alpha, beta, ratio) { 302 | 303 | var arr = getBezierCurveVectorArr(p0, p1, p2, p3, n); 304 | 305 | var strArr = ['M']; 306 | 307 | arr.forEach(function (item, k) { 308 | 309 | var ret = getProjection(anchor, d, alpha, beta, ratio, [item.x, item.y, item.z]); 310 | 311 | strArr.push(ret.x); 312 | 313 | strArr.push(ret.y); 314 | 315 | if (k < arr.length - 1) { 316 | strArr.push('L'); 317 | } 318 | }); 319 | 320 | return strArr.join(' '); 321 | } 322 | 323 | function getOneNewControl(control) { 324 | // refer: https://en.wikipedia.org/wiki/Bézier_triangle 325 | // a bizier triangle can be converted into two smaller bizier triangles of different controls 326 | 327 | // let us calculate with discretion 328 | var a3 = control.a3, 329 | a2b = control.a2b, 330 | ab2 = control.ab2, 331 | b3 = control.b3, 332 | a2y = control.a2y, 333 | aby = control.aby, 334 | b2y = control.b2y, 335 | ay2 = control.ay2, 336 | by2 = control.by2, 337 | y3 = control.y3; 338 | 339 | 340 | var newControl = {}; 341 | 342 | // a3' = a3, b <-> y 343 | newControl.a3 = [a3[0], a3[1], a3[2]]; 344 | 345 | // a2b' = 1/2*a3 + 1/2*a2b, b <-> y 346 | newControl.a2b = [1 / 2 * a3[0] + 1 / 2 * a2b[0], 1 / 2 * a3[1] + 1 / 2 * a2b[1], 1 / 2 * a3[2] + 1 / 2 * a2b[2]]; 347 | 348 | // ab2' = 1/4*a3 + 1/2*a2b + 1/4*ab2, b <-> y 349 | newControl.ab2 = [1 / 4 * a3[0] + 1 / 2 * a2b[0] + 1 / 4 * ab2[0], 1 / 4 * a3[1] + 1 / 2 * a2b[1] + 1 / 4 * ab2[1], 1 / 4 * a3[2] + 1 / 2 * a2b[2] + 1 / 4 * ab2[2]]; 350 | // newControl.ab2 = [ 351 | // 1/2*a2b[0] + 1/2*ab2[0], 352 | // 1/2*a2b[1] + 1/2*ab2[1], 353 | // 1/2*a2b[2] + 1/2*ab2[2] 354 | // ] 355 | 356 | 357 | // b3 = 1/8*a3 + 3/8*a2b + 3/8*ab2 + 1/8*b3, b <-> y 358 | newControl.b3 = [1 / 8 * a3[0] + 3 / 8 * a2b[0] + 3 / 8 * ab2[0] + 1 / 8 * b3[0], 1 / 8 * a3[1] + 3 / 8 * a2b[1] + 3 / 8 * ab2[1] + 1 / 8 * b3[1], 1 / 8 * a3[2] + 3 / 8 * a2b[2] + 3 / 8 * ab2[2] + 1 / 8 * b3[2]]; 359 | // newControl.b3 = [ 360 | // 1/2*ab2[0] + 1/2*b3[0], 361 | // 1/2*ab2[1] + 1/2*b3[1], 362 | // 1/2*ab2[2] + 1/2*b3[2] 363 | // ]; 364 | 365 | // a2y = a2y, b <-> y 366 | newControl.a2y = [a2y[0], a2y[1], a2y[2]]; 367 | 368 | // aby = 1/2*a2y + 1/2*aby, b <-> y 369 | newControl.aby = [1 / 2 * a2y[0] + 1 / 2 * aby[0], 1 / 2 * a2y[1] + 1 / 2 * aby[1], 1 / 2 * a2y[2] + 1 / 2 * aby[2]]; 370 | 371 | // b2y = 1/4*a2y + 1/2*aby + 1/4*b2y, b <-> y 372 | newControl.b2y = [1 / 4 * a2y[0] + 1 / 2 * aby[0] + 1 / 4 * b2y[0], 1 / 4 * a2y[1] + 1 / 2 * aby[1] + 1 / 4 * b2y[1], 1 / 4 * a2y[2] + 1 / 2 * aby[2] + 1 / 4 * b2y[2]]; 373 | // newControl.b2y = [ 374 | // 1/2*aby[0] + 1/2*b2y[0], 375 | // 1/2*aby[1] + 1/2*b2y[1], 376 | // 1/2*aby[2] + 1/2*b2y[2] 377 | // ] 378 | 379 | // ay2 = ay2, b <-> y 380 | newControl.ay2 = [ay2[0], ay2[1], ay2[2]]; 381 | 382 | // by2 = 1/2*ay2 + 1/2*by2, b <-> y 383 | newControl.by2 = [1 / 2 * ay2[0] + 1 / 2 * by2[0], 1 / 2 * ay2[1] + 1 / 2 * by2[1], 1 / 2 * ay2[2] + 1 / 2 * by2[2]]; 384 | 385 | // y3 = y3, b <-> y 386 | newControl.y3 = [y3[0], y3[1], y3[2]]; 387 | 388 | var tcontrl = { 389 | a3: newControl.y3, 390 | a2b: newControl.ay2, 391 | ab2: newControl.a2y, 392 | b3: newControl.a3, 393 | a2y: newControl.by2, 394 | aby: newControl.aby, 395 | b2y: newControl.a2b, 396 | ay2: newControl.b2y, 397 | by2: newControl.ab2, 398 | y3: newControl.b3 399 | }; 400 | 401 | return tcontrl; 402 | } 403 | 404 | function getTwoNewControls(control) { 405 | return [getOneNewControl(control), getOneNewControl({ 406 | a3: control.b3, 407 | a2b: control.ab2, 408 | ab2: control.a2b, 409 | b3: control.a3, 410 | a2y: control.b2y, 411 | aby: control.aby, 412 | b2y: control.a2y, 413 | ay2: control.by2, 414 | by2: control.ay2, 415 | y3: control.y3 416 | })]; 417 | } 418 | 419 | function getBezierSurfacePath(matrix, density, anchor, d, alpha, beta, ratio) { 420 | 421 | var str = ''; 422 | 423 | var arr = []; 424 | 425 | var recursiveTime = density; 426 | 427 | var possibleDraw = function possibleDraw(control, n) { 428 | 429 | var controls = getTwoNewControls(control); 430 | var control1 = controls[0]; 431 | var control2 = controls[1]; 432 | 433 | if (n == recursiveTime) { 434 | 435 | var strArr = [], 436 | ret = void 0; 437 | 438 | strArr.push('M'); 439 | ret = getProjection(anchor, d, alpha, beta, ratio, control1.a3); 440 | strArr.push(ret.x); 441 | strArr.push(ret.y); 442 | 443 | strArr.push('L'); 444 | ret = getProjection(anchor, d, alpha, beta, ratio, control1.b3); 445 | strArr.push(ret.x); 446 | strArr.push(ret.y); 447 | 448 | strArr.push('L'); 449 | ret = getProjection(anchor, d, alpha, beta, ratio, control1.y3); 450 | strArr.push(ret.x); 451 | strArr.push(ret.y); 452 | 453 | strArr.push('Z'); 454 | 455 | str += strArr.join(' ') + ' '; 456 | 457 | arr.push({ 458 | d: strArr.join(' '), 459 | center: [(control1.a3[0] + control1.b3[0] + control1.y3[0]) / 3, (control1.a3[1] + control1.b3[1] + control1.y3[1]) / 3, (control1.a3[2] + control1.b3[2] + control1.y3[2]) / 3] 460 | }); 461 | 462 | strArr = []; 463 | 464 | strArr.push('M'); 465 | ret = getProjection(anchor, d, alpha, beta, ratio, control2.b3); 466 | strArr.push(ret.x); 467 | strArr.push(ret.y); 468 | 469 | strArr.push('L'); 470 | ret = getProjection(anchor, d, alpha, beta, ratio, control2.a3); 471 | strArr.push(ret.x); 472 | strArr.push(ret.y); 473 | 474 | strArr.push('L'); 475 | ret = getProjection(anchor, d, alpha, beta, ratio, control2.y3); 476 | strArr.push(ret.x); 477 | strArr.push(ret.y); 478 | 479 | str += strArr.join(' ') + ' '; 480 | 481 | strArr.push('Z'); 482 | 483 | arr.push({ 484 | d: strArr.join(' '), 485 | center: [(control2.a3[0] + control2.b3[0] + control2.y3[0]) / 3, (control2.a3[1] + control2.b3[1] + control2.y3[1]) / 3, (control2.a3[2] + control2.b3[2] + control2.y3[2]) / 3] 486 | }); 487 | } else { 488 | 489 | possibleDraw(control1, n + 1); 490 | possibleDraw(control2, n + 1); 491 | } 492 | }; 493 | 494 | var control1 = { 495 | b3: matrix[0][0], 496 | ab2: matrix[1][0], 497 | b2y: matrix[1][1], 498 | a2b: matrix[2][0], 499 | aby: matrix[2][1], 500 | by2: matrix[2][2], 501 | a3: matrix[3][0], 502 | a2y: matrix[3][1], 503 | ay2: matrix[3][2], 504 | y3: matrix[3][3] 505 | }; 506 | 507 | possibleDraw(control1, 0); 508 | 509 | var control2 = { 510 | b3: matrix[3][3], 511 | ab2: matrix[2][3], 512 | b2y: matrix[2][2], 513 | a2b: matrix[1][3], 514 | aby: matrix[1][2], 515 | by2: matrix[1][1], 516 | a3: matrix[0][3], 517 | a2y: matrix[0][2], 518 | ay2: matrix[0][1], 519 | y3: matrix[0][0] 520 | }; 521 | 522 | possibleDraw(control2, 0); 523 | 524 | return arr; 525 | } 526 | 527 | function getBezierTrianglePath(control, density, anchor, d, alpha, beta, ratio) { 528 | 529 | var arr = []; 530 | 531 | var recursiveTime = density; 532 | 533 | var possibleDraw = function possibleDraw(control, n) { 534 | 535 | var controls = getTwoNewControls(control); 536 | var control1 = controls[0]; 537 | var control2 = controls[1]; 538 | 539 | if (n == recursiveTime) { 540 | 541 | var strArr = [], 542 | ret = void 0; 543 | 544 | strArr.push('M'); 545 | ret = getProjection(anchor, d, alpha, beta, ratio, control1.a3); 546 | strArr.push(ret.x); 547 | strArr.push(ret.y); 548 | 549 | strArr.push('L'); 550 | ret = getProjection(anchor, d, alpha, beta, ratio, control1.b3); 551 | strArr.push(ret.x); 552 | strArr.push(ret.y); 553 | 554 | strArr.push('L'); 555 | ret = getProjection(anchor, d, alpha, beta, ratio, control1.y3); 556 | strArr.push(ret.x); 557 | strArr.push(ret.y); 558 | 559 | strArr.push('Z'); 560 | 561 | arr.push({ 562 | d: strArr.join(' '), 563 | center: [(control1.a3[0] + control1.b3[0] + control1.y3[0]) / 3, (control1.a3[1] + control1.b3[1] + control1.y3[1]) / 3, (control1.a3[2] + control1.b3[2] + control1.y3[2]) / 3] 564 | }); 565 | 566 | strArr = []; 567 | 568 | strArr.push('M'); 569 | ret = getProjection(anchor, d, alpha, beta, ratio, control2.b3); 570 | strArr.push(ret.x); 571 | strArr.push(ret.y); 572 | 573 | strArr.push('L'); 574 | ret = getProjection(anchor, d, alpha, beta, ratio, control2.a3); 575 | strArr.push(ret.x); 576 | strArr.push(ret.y); 577 | 578 | strArr.push('L'); 579 | ret = getProjection(anchor, d, alpha, beta, ratio, control2.y3); 580 | strArr.push(ret.x); 581 | strArr.push(ret.y); 582 | 583 | strArr.push('Z'); 584 | 585 | arr.push({ 586 | d: strArr.join(' '), 587 | center: [(control2.a3[0] + control2.b3[0] + control2.y3[0]) / 3, (control2.a3[1] + control2.b3[1] + control2.y3[1]) / 3, (control2.a3[2] + control2.b3[2] + control2.y3[2]) / 3] 588 | }); 589 | } else { 590 | 591 | possibleDraw(control1, n + 1); 592 | possibleDraw(control2, n + 1); 593 | } 594 | }; 595 | 596 | possibleDraw(control, 0); 597 | 598 | return arr; 599 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "svg-3d-builder", 3 | "version": "0.0.3", 4 | "description": "", 5 | "main": "lib/index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [ 10 | "d3", 11 | "3d" 12 | ], 13 | "author": "captainwz", 14 | "license": "Apache", 15 | "devDependencies": { 16 | "antd-mobile": "^2.2.2", 17 | "babel-core": "^6.26.3", 18 | "babel-loader": "^7.1.5", 19 | "babel-preset-env": "^1.7.0", 20 | "babel-preset-react": "^6.24.1", 21 | "babel-preset-stage-0": "^6.24.1", 22 | "gulp": "^3.9.1", 23 | "gulp-babel": "^7.0.1", 24 | "react": "^16.4.1", 25 | "react-dom": "^16.4.1", 26 | "react-tappable": "^1.0.4", 27 | "webpack": "^4.16.2", 28 | "webpack-cli": "^3.1.0" 29 | }, 30 | "dependencies": { 31 | "antd": "^3.7.3", 32 | "d3": "^5.5.0" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/es5.js: -------------------------------------------------------------------------------- 1 | import Builder from './index'; 2 | window.Builder = Builder; -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import * as d3 from 'd3'; 2 | import { 3 | getLinePath, 4 | getBezierCurvePath, 5 | getBezierSurfacePath, 6 | getBezierTrianglePath, 7 | getCameraPoint 8 | } from './util'; 9 | 10 | class Builder { 11 | 12 | constructor () { 13 | 14 | Object.assign(this, { 15 | svg: null, 16 | selector: null, 17 | bezierSurfaceGroup: null, 18 | lineList: [], 19 | lineIndex: null, 20 | bezierCurveList: [], 21 | bezierCurveIndex: null, 22 | bezierSurfaceList: [], 23 | bezierSurfaceIndex: null, 24 | bezierTriangleList: [], 25 | bezierTriangleIndex: null, 26 | camera: { 27 | anchor: [0, 0, 0], 28 | d: 300, 29 | alpha: 0, 30 | beta: 0 31 | }, 32 | screen: { 33 | ratio: 0.5, 34 | offsetX: 0, 35 | offsetY: 0 36 | }, 37 | axis: { 38 | show: false, 39 | xLength: 200, 40 | xColor: '#000', 41 | yLength: 200, 42 | yColor: '#000', 43 | zLength: 200, 44 | zColor: '#000', 45 | xRef: null, 46 | yRef: null, 47 | zRef: null 48 | } 49 | }) 50 | 51 | } 52 | 53 | reset () { 54 | 55 | if (this.selector) { 56 | this.selector.remove(); 57 | } 58 | 59 | Object.assign(this, { 60 | svg: null, 61 | selector: null, 62 | bezierSurfaceGroup: null, 63 | lineList: [], 64 | lineIndex: null, 65 | bezierCurveList: [], 66 | bezierCurveIndex: null, 67 | bezierSurfaceList: [], 68 | bezierSurfaceIndex: null, 69 | bezierTriangleList: [], 70 | bezierTriangleIndex: null, 71 | camera: { 72 | anchor: [0, 0, 0], 73 | d: 300, 74 | alpha: 0, 75 | beta: 0 76 | }, 77 | screen: { 78 | ratio: 0.5, 79 | offsetX: 0, 80 | offsetY: 0 81 | }, 82 | axis: { 83 | show: false, 84 | xLength: 200, 85 | xColor: '#000', 86 | yLength: 200, 87 | yColor: '#000', 88 | zLength: 200, 89 | zColor: '#000', 90 | xRef: null, 91 | yRef: null, 92 | zRef: null 93 | } 94 | }) 95 | 96 | return this; 97 | 98 | } 99 | 100 | setCamera (opt) { 101 | 102 | if (!opt) { 103 | opt = {} 104 | } 105 | 106 | const { 107 | anchor = this.camera.anchor, 108 | d = this.camera.d, 109 | alpha = this.camera.alpha, 110 | beta = this.camera.beta 111 | } = opt; 112 | 113 | if (!anchor instanceof Array || 114 | anchor.length != 3 115 | ) { 116 | throw new Error 117 | } 118 | 119 | this.camera = { 120 | anchor, 121 | d, 122 | alpha, 123 | beta 124 | } 125 | 126 | return this; 127 | 128 | } 129 | 130 | setScreen (opt) { 131 | 132 | if (!opt) { 133 | opt = {} 134 | } 135 | 136 | const { 137 | ratio = this.screen.ratio, 138 | offsetX = this.screen.offsetX, 139 | offsetY = this.screen.offsetY 140 | } = opt; 141 | 142 | this.screen = { 143 | ratio, 144 | offsetX, 145 | offsetY 146 | } 147 | 148 | this.selector.attr('transform', `translate(${offsetX}, ${offsetY})`) 149 | 150 | return this; 151 | } 152 | 153 | setAxis (opt) { 154 | 155 | if (!opt) { 156 | opt = {} 157 | } 158 | 159 | const { 160 | show = this.axis.show, 161 | xLength = this.axis.xLength, 162 | xColor = this.axis.xColor, 163 | yLength = this.axis.yLength, 164 | yColor = this.axis.yColor, 165 | zLength = this.axis.zLength, 166 | zColor = this.axis.zColor, 167 | 168 | } = opt; 169 | 170 | this.axis = { 171 | show, 172 | xLength, 173 | xColor, 174 | yLength, 175 | yColor, 176 | zLength, 177 | zColor, 178 | xRef: this.axis.xRef ? this.axis.xRef : this.selector.append('path'), 179 | yRef: this.axis.yRef ? this.axis.yRef : this.selector.append('path'), 180 | zRef: this.axis.zRef ? this.axis.zRef : this.selector.append('path') 181 | } 182 | 183 | return this; 184 | } 185 | 186 | 187 | 188 | select (selector) { 189 | this.svg = d3.select(selector); 190 | this.selector = this.svg.append('g'); 191 | this.bezierSurfaceGroup = this.selector.append('g'); 192 | 193 | return this; 194 | } 195 | 196 | drawLine (d) { 197 | 198 | this.lineList.push({ 199 | ref: this.selector.append('path'), 200 | originPath: d, 201 | stroke: '#000', 202 | fill: 'transparent' 203 | }); 204 | 205 | this.lineIndex = this.lineList.length - 1; 206 | 207 | return this; 208 | } 209 | 210 | redrawLine (d, index) { 211 | if (index == undefined) { 212 | index = this.lineIndex; 213 | } 214 | 215 | this.lineList[index].originPath = d; 216 | 217 | return this; 218 | } 219 | 220 | drawBezierCurve (p0, p1, p2, p3, n) { 221 | this.bezierCurveList.push({ 222 | ref: this.selector.append('path'), 223 | p0, 224 | p1, 225 | p2, 226 | p3, 227 | n, 228 | stroke: '#000', 229 | fill: 'transparent' 230 | }) 231 | 232 | this.bezierCurveIndex = this.bezierCurveList.length - 1; 233 | 234 | return this; 235 | } 236 | 237 | redrawBezierCurve (p0, p1, p2, p3, n, index) { 238 | if (index == undefined) { 239 | index = this.bezierCurveIndex; 240 | } 241 | 242 | this.bezierCurveList[index].p0 = p0; 243 | this.bezierCurveList[index].p1 = p1; 244 | this.bezierCurveList[index].p2 = p2; 245 | this.bezierCurveList[index].p3 = p3; 246 | this.bezierCurveList[index].n = n; 247 | 248 | return this; 249 | } 250 | 251 | drawBezierSurface (matrix, density) { 252 | this.bezierSurfaceList.push({ 253 | ref: this.selector.append('path'), 254 | matrix, 255 | density, 256 | stroke: '#000', 257 | strokeWidth: 0.5, 258 | fill: 'transparent' 259 | }) 260 | 261 | this.bezierSurfaceIndex = this.bezierSurfaceList.length - 1; 262 | 263 | return this; 264 | } 265 | 266 | redrawBezierSurface (matrix, density, index) { 267 | if (index == undefined) { 268 | index = this.bezierSurfaceIndex; 269 | } 270 | 271 | this.bezierSurfaceList[index].matrix = matrix; 272 | this.bezierSurfaceList[index].density = density; 273 | 274 | return this; 275 | } 276 | 277 | drawBezierTriangle (control, density) { 278 | 279 | this.bezierTriangleList.push({ 280 | control, 281 | density, 282 | stroke: '#000', 283 | strokeWidth: 0.5, 284 | fill: 'transparent' 285 | }) 286 | 287 | this.bezierTriangleIndex = this.bezierTriangleList.length - 1; 288 | return this; 289 | 290 | } 291 | 292 | redrawBezierTriangle (control, density, index) { 293 | if (index == undefined) { 294 | index = this.bezierTriangleIndex; 295 | } 296 | 297 | this.bezierTriangleList[index].control = control; 298 | this.bezierTriangleList[index].density = density; 299 | 300 | return this; 301 | } 302 | 303 | setLineStroke (color, index) { 304 | 305 | if (index == undefined) { 306 | index = this.lineIndex; 307 | } 308 | 309 | this.lineList[index].stroke = color; 310 | return this; 311 | } 312 | 313 | setBezierCurveStroke (color, index) { 314 | if (index == undefined) { 315 | index = this.bezierCurveIndex; 316 | } 317 | 318 | this.bezierCurveList[index].stroke = color; 319 | return this; 320 | } 321 | 322 | setBezierSurfaceStroke (color, index) { 323 | if (index == undefined) { 324 | index = this.bezierSurfaceIndex; 325 | } 326 | 327 | this.bezierSurfaceList[index].stroke = color; 328 | return this; 329 | } 330 | 331 | setBezierTriangleStroke (color, index) { 332 | if (index == undefined) { 333 | index = this.bezierTriangleIndex; 334 | } 335 | 336 | this.bezierTriangleList[index].stroke = color; 337 | return this; 338 | } 339 | 340 | setBezierSurfaceStrokeWidth (width, index) { 341 | if (index == undefined) { 342 | index = this.bezierSurfaceIndex; 343 | } 344 | 345 | this.bezierSurfaceList[index].strokeWidth = width; 346 | return this; 347 | } 348 | 349 | setBezierTriangleStrokeWidth (width, index) { 350 | if (index == undefined) { 351 | index = this.bezierTriangleIndex; 352 | } 353 | this.bezierTriangleList[index].strokeWidth = width; 354 | return this; 355 | } 356 | 357 | setBezierSurfaceFill (color, index) { 358 | if (index == undefined) { 359 | index = this.bezierSurfaceIndex; 360 | } 361 | 362 | this.bezierSurfaceList[index].fill = color; 363 | return this; 364 | } 365 | 366 | setBezierTriangleFill (color, index) { 367 | if (index == undefined) { 368 | index = this.bezierTriangleIndex; 369 | } 370 | this.bezierTriangleList[index].fill = color; 371 | return this; 372 | } 373 | 374 | action () { 375 | let camera = this.camera; 376 | let screen = this.screen; 377 | 378 | if (this.axis.show) { 379 | 380 | if (this.axis.xRef) { 381 | let d = `M 0 0 0 L ${this.axis.xLength} 0 0 M ${this.axis.xLength} 0 0 l -5 5 0 M ${this.axis.xLength} 0 0 l -5 -5 0`; 382 | this 383 | .axis 384 | .xRef 385 | .transition() 386 | .attr('d', getLinePath(d, camera.anchor, camera.d, camera.alpha, camera.beta, screen.ratio)) 387 | .attr('stroke', this.axis.xColor) 388 | .attr('fill', 'transparent') 389 | } 390 | 391 | if (this.axis.yRef) { 392 | let d = `M 0 0 0 L 0 ${this.axis.yLength} 0 M 0 ${this.axis.yLength} 0 l 5 -5 0 M 0 ${this.axis.yLength} 0 l -5 -5 0`; 393 | this 394 | .axis 395 | .yRef 396 | .transition() 397 | .attr('d', getLinePath(d, camera.anchor, camera.d, camera.alpha, camera.beta, screen.ratio)) 398 | .attr('stroke', this.axis.yColor) 399 | .attr('fill', 'transparent') 400 | } 401 | 402 | if (this.axis.zRef) { 403 | let d = `M 0 0 0 L 0 0 ${this.axis.zLength} M 0 0 ${this.axis.zLength} l 5 0 -5 M 0 0 ${this.axis.zLength} l -5 0 -5`; 404 | this 405 | .axis 406 | .zRef 407 | .transition() 408 | .attr('d', getLinePath(d, camera.anchor, camera.d, camera.alpha, camera.beta, screen.ratio)) 409 | .attr('stroke', this.axis.zColor) 410 | .attr('fill', 'transparent') 411 | } 412 | 413 | } 414 | 415 | this.lineList.forEach((path, k) => { 416 | 417 | path 418 | .ref 419 | .transition() 420 | .attr('d', getLinePath(path.originPath, camera.anchor, camera.d, camera.alpha, camera.beta, screen.ratio)) 421 | .attr('stroke', path.stroke) 422 | .attr('fill', path.fill) 423 | }) 424 | 425 | 426 | this.bezierCurveList.forEach((path, k) => { 427 | 428 | path 429 | .ref 430 | .transition() 431 | .attr('d', getBezierCurvePath(path.p0, path.p1, path.p2, path.p3, path.n, camera.anchor, camera.d, camera.alpha, camera.beta, screen.ratio)) 432 | .attr('stroke', path.stroke) 433 | .attr('fill', path.fill) 434 | 435 | }) 436 | 437 | this.bezierSurfaceGroup.selectAll('*').remove(); 438 | 439 | let arr = []; 440 | 441 | this.bezierSurfaceList.forEach((path, k) => { 442 | 443 | let a = getBezierSurfacePath(path.matrix, path.density, camera.anchor, camera.d, camera.alpha, camera.beta, screen.ratio); 444 | 445 | a = a.map(item => { 446 | return Object.assign(item, { 447 | stroke: path.stroke, 448 | fill: path.fill, 449 | strokeWidth: path.strokeWidth 450 | }) 451 | }); 452 | 453 | arr = arr.concat(a); 454 | 455 | /* 456 | path 457 | .ref 458 | .transition() 459 | .attr('d', getBezierSurfacePath(path.matrix, path.density, camera.anchor, camera.d, camera.alpha, camera.beta, screen.ratio)) 460 | .attr('stroke', path.stroke) 461 | .attr('fill', path.fill)*/ 462 | 463 | }) 464 | 465 | this.bezierTriangleList.forEach((triangle, k) => { 466 | let a = getBezierTrianglePath(triangle.control, triangle.density, camera.anchor, camera.d, camera.alpha, camera.beta, screen.ratio); 467 | 468 | a = a.map(item => { 469 | return Object.assign(item, { 470 | stroke: triangle.stroke, 471 | fill: triangle.fill, 472 | strokeWidth: triangle.strokeWidth 473 | }) 474 | }); 475 | 476 | arr = arr.concat(a); 477 | }) 478 | 479 | let cameraPoint = getCameraPoint(camera.anchor, camera.d, camera.alpha, camera.beta) 480 | 481 | arr.sort((aa, bb) => { 482 | let a = aa.center; 483 | let b = bb.center; 484 | 485 | const x = cameraPoint[0]; 486 | const y = cameraPoint[1]; 487 | const z = cameraPoint[2]; 488 | const adx = (a[0] - x)*(a[0] - x); 489 | const ady = (a[1] - y)*(a[1] - y); 490 | const adz = (a[2] - z)*(a[2] - z); 491 | const bdx = (b[0] - x)*(b[0] - x); 492 | const bdy = (b[1] - y)*(b[1] - y); 493 | const bdz = (b[2] - z)*(b[2] - z); 494 | 495 | return (adx + ady + adz) < (bdx + bdy + bdz) 496 | 497 | }).forEach(item => { 498 | this 499 | .bezierSurfaceGroup 500 | .append('path') 501 | .attr('d', item.d) 502 | .attr('stroke', item.stroke) 503 | .attr('stroke-width', item.strokeWidth) 504 | .attr('fill', item.fill) 505 | 506 | }) 507 | 508 | 509 | return this; 510 | } 511 | 512 | 513 | } 514 | 515 | export default new Builder(); -------------------------------------------------------------------------------- /src/util.js: -------------------------------------------------------------------------------- 1 | export function getCameraPoint (anchor, d, alpha, beta) { 2 | const ax = anchor[0]; // anchor x 3 | const ay = anchor[1]; // anchor y 4 | const az = anchor[2]; // anchor z 5 | 6 | let dx = d*Math.cos(beta)*Math.sin(alpha); 7 | let dy = d*Math.sin(beta); 8 | let dz = d*Math.cos(alpha)*Math.cos(beta); 9 | 10 | return [ 11 | ax + dx, 12 | ay + dy, 13 | az + dz 14 | ] 15 | } 16 | 17 | export function getProjection (anchor, d, alpha, beta, ratio, origin) { 18 | const ax = anchor[0]; // anchor x 19 | const ay = anchor[1]; // anchor y 20 | const az = anchor[2]; // anchor z 21 | 22 | const ox = origin[0]; // origin x 23 | const oy = origin[1]; // origin y 24 | const oz = origin[2]; // origin z 25 | 26 | let dx = d*Math.cos(beta)*Math.sin(alpha); 27 | let dy = d*Math.sin(beta); 28 | // let dy = d*Math.cos(alpha)*Math.sin(beta); 29 | let dz = d*Math.cos(alpha)*Math.cos(beta); 30 | 31 | let mod = Math.sqrt(dx*dx + dy*dy + dz*dz); 32 | let nv = [-dx/mod, -dy/mod, -dz/mod]; // normal vector 33 | 34 | let so = [ax + (1 - ratio)*dx, ay + (1 - ratio)*dy, az + (1 - ratio)*dz]; // screen o point 35 | 36 | 37 | 38 | let c0 = (thita) => { 39 | if (Math.cos(thita) == 0) { 40 | return 1; 41 | } else { 42 | return Math.cos(thita)/Math.abs(Math.cos(thita)) > 0 ? 1 : -1; 43 | } 44 | } 45 | 46 | let c1 = (thita) => { 47 | if (Math.sin(thita) == 0) { 48 | return 1; 49 | } else { 50 | return Math.sin(thita)/Math.abs(Math.sin(thita)) > 0 ? 1 : -1; 51 | } 52 | } 53 | // coefficient 54 | 55 | // let sy = [0, 56 | // Math.cos(beta)/Math.sqrt(Math.cos(beta)*Math.cos(beta) + Math.sin(beta)*Math.sin(beta)), 57 | // -Math.sin(beta)/Math.sqrt(Math.cos(beta)*Math.cos(beta) + Math.sin(beta)*Math.sin(beta)) 58 | // ] 59 | 60 | // let f = (dy*dy + dz*dz); 61 | // let m = Math.sqrt(f*f + dy*dy*dx*dx + dz*dz*dx*dx); 62 | // let sx = [c0(alpha)*f/m, -1*c0(alpha)*dy*dx/m, -1*c0(alpha)*dz*dx/m]; 63 | // screen x axis 64 | 65 | let syy = [-1*Math.sin(beta)*Math.sin(alpha), Math.cos(beta), -1*Math.sin(beta)*Math.cos(alpha)]; 66 | 67 | let yf = Math.sqrt(syy[0]*syy[0] + syy[1]*syy[1] + syy[2]*syy[2]); 68 | let sy = [syy[0]/yf, syy[1]/yf, syy[2]/yf]; 69 | 70 | let sx = [Math.cos(alpha), 0, -Math.sin(alpha)] 71 | 72 | 73 | let ray = [ox - ax - dx, oy - ay - dy, oz - az - dz]; 74 | let p = d*ratio/(ray[0]*nv[0] + ray[1]*nv[1] + ray[2]*nv[2]); 75 | let focus = [ax + dx + ray[0]*p, ay + dy + ray[1]*p, az + dz +ray[2]*p]; 76 | let rf = [focus[0] - so[0], focus[1] - so[1], focus[2] - so[2]]; // relative focus 77 | let rx = rf[0]*sx[0] + rf[1]*sx[1] + rf[2]*sx[2]; 78 | let ry = rf[0]*sy[0] + rf[1]*sy[1] + rf[2]*sy[2]; 79 | 80 | return { 81 | x: rx, 82 | y: ry 83 | } 84 | 85 | } 86 | 87 | export function getLinePath (path, anchor, d, alpha, beta, ratio) { 88 | let arr = path 89 | .replace(/\,/g, ' ') 90 | .replace(/[Mm]/g, (r) => { return r + ' '}) 91 | .replace(/[LlCcSsQqTt]/g, (r) => { return ' ' + r + ' '}) 92 | .replace(/[Zz]/g, (r) => { return ' ' + r}) 93 | .replace(/\s+/g, ',') 94 | .split(','); 95 | 96 | let newArr = [] 97 | 98 | const f = (origin) => { 99 | return getProjection(anchor, d, alpha, beta, ratio, origin); 100 | } 101 | 102 | arr = arr.map(item => { 103 | if (/\d/.test(item)) { 104 | return Number(item); 105 | } else { 106 | return item; 107 | } 108 | }); 109 | 110 | arr.forEach((item, k) => { 111 | if (item == 'm' || item == 'M') { 112 | newArr.push('M'); 113 | let r = f([arr[k + 1], arr[k + 2], arr[k + 3]]); 114 | newArr.push(r.x); 115 | newArr.push(r.y); 116 | } 117 | 118 | if (item == 'L') { 119 | newArr.push('L'); 120 | let r = f([arr[k + 1], arr[k + 2], arr[k + 3]]); 121 | newArr.push(r.x); 122 | newArr.push(r.y); 123 | } 124 | 125 | if (item == 'l') { 126 | newArr.push('L'); 127 | arr[k + 1] = arr[k - 3] + arr[k + 1]; 128 | arr[k + 2] = arr[k - 2] + arr[k + 2]; 129 | arr[k + 3] = arr[k - 1] + arr[k + 3]; 130 | let r = f([arr[k + 1], arr[k + 2], arr[k + 3]]); 131 | newArr.push(r.x); 132 | newArr.push(r.y); 133 | } 134 | 135 | if (item == 'C') { 136 | newArr.push('C'); 137 | 138 | let r = f([arr[k + 1], arr[k + 2], arr[k + 3]]); 139 | newArr.push(r.x); 140 | newArr.push(r.y); 141 | 142 | r = f([arr[k + 4], arr[k + 5], arr[k + 6]]); 143 | newArr.push(r.x); 144 | newArr.push(r.y); 145 | 146 | r = f([arr[k + 7], arr[k + 8], arr[k + 9]]); 147 | newArr.push(r.x); 148 | newArr.push(r.y); 149 | } 150 | 151 | if (item == 'c') { 152 | newArr.push('C'); 153 | 154 | arr[k + 1] = arr[k - 3] + arr[k + 1]; 155 | arr[k + 2] = arr[k - 2] + arr[k + 2]; 156 | arr[k + 3] = arr[k - 1] + arr[k + 3]; 157 | let r = f([arr[k + 1], arr[k + 2], arr[k + 3]]); 158 | newArr.push(r.x); 159 | newArr.push(r.y); 160 | 161 | arr[k + 4] = arr[k - 3] + arr[k + 4]; 162 | arr[k + 5] = arr[k - 2] + arr[k + 5]; 163 | arr[k + 6] = arr[k - 1] + arr[k + 6]; 164 | r = f([arr[k + 4], arr[k + 5], arr[k + 6]]); 165 | newArr.push(r.x); 166 | newArr.push(r.y); 167 | 168 | arr[k + 7] = arr[k - 3] + arr[k + 7]; 169 | arr[k + 8] = arr[k - 2] + arr[k + 8]; 170 | arr[k + 9] = arr[k - 1] + arr[k + 9]; 171 | r = f([arr[k + 7], arr[k + 8], arr[k + 9]]); 172 | newArr.push(r.x); 173 | newArr.push(r.y); 174 | } 175 | 176 | if (item == 'S') { 177 | newArr.push('S'); 178 | let r = f([arr[k + 1], arr[k + 2], arr[k + 3]]); 179 | newArr.push(r.x); 180 | newArr.push(r.y); 181 | 182 | r = f([arr[k + 4], arr[k + 5], arr[k + 6]]); 183 | newArr.push(r.x); 184 | newArr.push(r.y); 185 | } 186 | 187 | if (item == 's') { 188 | newArr.push('S'); 189 | 190 | arr[k + 1] = arr[k - 3] + arr[k + 1]; 191 | arr[k + 2] = arr[k - 2] + arr[k + 2]; 192 | arr[k + 3] = arr[k - 1] + arr[k + 3]; 193 | let r = f([arr[k + 1], arr[k + 2], arr[k + 3]]); 194 | newArr.push(r.x); 195 | newArr.push(r.y); 196 | 197 | arr[k + 4] = arr[k - 3] + arr[k + 4]; 198 | arr[k + 5] = arr[k - 2] + arr[k + 5]; 199 | arr[k + 6] = arr[k - 1] + arr[k + 6]; 200 | r = f([arr[k + 4], arr[k + 5], arr[k + 6]]); 201 | newArr.push(r.x); 202 | newArr.push(r.y); 203 | } 204 | 205 | if (item == 'Q') { 206 | newArr.push('Q'); 207 | let r = f([arr[k + 1], arr[k + 2], arr[k + 3]]); 208 | newArr.push(r.x); 209 | newArr.push(r.y); 210 | 211 | r = f([arr[k + 4], arr[k + 5], arr[k + 6]]); 212 | newArr.push(r.x); 213 | newArr.push(r.y); 214 | } 215 | 216 | if (item == 'q') { 217 | newArr.push('Q'); 218 | 219 | arr[k + 1] = arr[k - 3] + arr[k + 1]; 220 | arr[k + 2] = arr[k - 2] + arr[k + 2]; 221 | arr[k + 3] = arr[k - 1] + arr[k + 3]; 222 | let r = f([arr[k + 1], arr[k + 2], arr[k + 3]]); 223 | newArr.push(r.x); 224 | newArr.push(r.y); 225 | 226 | arr[k + 4] = arr[k - 3] + arr[k + 4]; 227 | arr[k + 5] = arr[k - 2] + arr[k + 5]; 228 | arr[k + 6] = arr[k - 1] + arr[k + 6]; 229 | r = f([arr[k + 4], arr[k + 5], arr[k + 6]]); 230 | newArr.push(r.x); 231 | newArr.push(r.y); 232 | } 233 | 234 | if (item == 'T') { 235 | newArr.push('T'); 236 | let r = f([arr[k + 1], arr[k + 2], arr[k + 3]]); 237 | newArr.push(r.x); 238 | newArr.push(r.y); 239 | } 240 | 241 | if (item == 't') { 242 | newArr.push('T'); 243 | arr[k + 1] = arr[k - 3] + arr[k + 1]; 244 | arr[k + 2] = arr[k - 2] + arr[k + 2]; 245 | arr[k + 3] = arr[k - 1] + arr[k + 3]; 246 | let r = f([arr[k + 1], arr[k + 2], arr[k + 3]]); 247 | newArr.push(r.x); 248 | newArr.push(r.y); 249 | } 250 | 251 | if (item == 'z' || item == 'Z') { 252 | newArr.push('Z'); 253 | } 254 | 255 | }) 256 | 257 | return newArr.join(' '); 258 | 259 | } 260 | 261 | const getBezierCurveVectorArr = (p0, p1, p2, p3, n) => { 262 | 263 | const x0 = p0[0]; 264 | const y0 = p0[1]; 265 | const z0 = p0[2]; 266 | 267 | const x1 = p1[0]; 268 | const y1 = p1[1]; 269 | const z1 = p1[2]; 270 | 271 | const x2 = p2[0]; 272 | const y2 = p2[1]; 273 | const z2 = p2[2]; 274 | 275 | const x3 = p3[0]; 276 | const y3 = p3[1]; 277 | const z3 = p3[2]; 278 | 279 | let arr = []; 280 | 281 | let k = 0; 282 | 283 | for (; k <= n; k ++) { 284 | let t = k/n; 285 | 286 | let bx = (1 - t)*(1 - t)*(1 - t)*x0 + 3*t*(1 - t)*(1 - t)*x1 + 3*t*t*(1 - t)*x2 + t*t*t*x3; 287 | let by = (1 - t)*(1 - t)*(1 - t)*y0 + 3*t*(1 - t)*(1 - t)*y1 + 3*t*t*(1 - t)*y2 + t*t*t*y3; 288 | let bz = (1 - t)*(1 - t)*(1 - t)*z0 + 3*t*(1 - t)*(1 - t)*z1 + 3*t*t*(1 - t)*z2 + t*t*t*z3; 289 | 290 | arr.push({ 291 | x: bx, 292 | y: by, 293 | z: bz 294 | }) 295 | } 296 | return arr; 297 | 298 | } 299 | 300 | export function getBezierCurvePath (p0, p1, p2, p3, n, anchor, d, alpha, beta, ratio) { 301 | 302 | let arr = getBezierCurveVectorArr(p0, p1, p2, p3, n); 303 | 304 | let strArr = ['M']; 305 | 306 | arr.forEach((item, k) => { 307 | 308 | let ret = getProjection(anchor, d, alpha, beta, ratio, [item.x, item.y, item.z]); 309 | 310 | strArr.push(ret.x); 311 | 312 | strArr.push(ret.y); 313 | 314 | if (k < arr.length - 1) { 315 | strArr.push('L'); 316 | } 317 | 318 | }) 319 | 320 | return strArr.join(' '); 321 | 322 | } 323 | 324 | function getOneNewControl (control) { 325 | // refer: https://en.wikipedia.org/wiki/Bézier_triangle 326 | // a bizier triangle can be converted into two smaller bizier triangles of different controls 327 | 328 | // let us calculate with discretion 329 | const { 330 | a3, a2b, ab2, b3, a2y, aby, b2y, ay2, by2, y3 331 | } = control; 332 | 333 | let newControl = {} 334 | 335 | // a3' = a3, b <-> y 336 | newControl.a3 = [ 337 | a3[0], 338 | a3[1], 339 | a3[2] 340 | ] 341 | 342 | // a2b' = 1/2*a3 + 1/2*a2b, b <-> y 343 | newControl.a2b = [ 344 | 1/2*a3[0] + 1/2*a2b[0], 345 | 1/2*a3[1] + 1/2*a2b[1], 346 | 1/2*a3[2] + 1/2*a2b[2] 347 | ] 348 | 349 | // ab2' = 1/4*a3 + 1/2*a2b + 1/4*ab2, b <-> y 350 | newControl.ab2 = [ 351 | 1/4*a3[0] + 1/2*a2b[0] + 1/4*ab2[0], 352 | 1/4*a3[1] + 1/2*a2b[1] + 1/4*ab2[1], 353 | 1/4*a3[2] + 1/2*a2b[2] + 1/4*ab2[2] 354 | ] 355 | // newControl.ab2 = [ 356 | // 1/2*a2b[0] + 1/2*ab2[0], 357 | // 1/2*a2b[1] + 1/2*ab2[1], 358 | // 1/2*a2b[2] + 1/2*ab2[2] 359 | // ] 360 | 361 | 362 | // b3 = 1/8*a3 + 3/8*a2b + 3/8*ab2 + 1/8*b3, b <-> y 363 | newControl.b3 = [ 364 | 1/8*a3[0] + 3/8*a2b[0] + 3/8*ab2[0] + 1/8*b3[0], 365 | 1/8*a3[1] + 3/8*a2b[1] + 3/8*ab2[1] + 1/8*b3[1], 366 | 1/8*a3[2] + 3/8*a2b[2] + 3/8*ab2[2] + 1/8*b3[2] 367 | ] 368 | // newControl.b3 = [ 369 | // 1/2*ab2[0] + 1/2*b3[0], 370 | // 1/2*ab2[1] + 1/2*b3[1], 371 | // 1/2*ab2[2] + 1/2*b3[2] 372 | // ]; 373 | 374 | // a2y = a2y, b <-> y 375 | newControl.a2y = [ 376 | a2y[0], 377 | a2y[1], 378 | a2y[2] 379 | ] 380 | 381 | // aby = 1/2*a2y + 1/2*aby, b <-> y 382 | newControl.aby = [ 383 | 1/2*a2y[0] + 1/2*aby[0], 384 | 1/2*a2y[1] + 1/2*aby[1], 385 | 1/2*a2y[2] + 1/2*aby[2], 386 | ] 387 | 388 | // b2y = 1/4*a2y + 1/2*aby + 1/4*b2y, b <-> y 389 | newControl.b2y = [ 390 | 1/4*a2y[0] + 1/2*aby[0] + 1/4*b2y[0], 391 | 1/4*a2y[1] + 1/2*aby[1] + 1/4*b2y[1], 392 | 1/4*a2y[2] + 1/2*aby[2] + 1/4*b2y[2] 393 | ] 394 | // newControl.b2y = [ 395 | // 1/2*aby[0] + 1/2*b2y[0], 396 | // 1/2*aby[1] + 1/2*b2y[1], 397 | // 1/2*aby[2] + 1/2*b2y[2] 398 | // ] 399 | 400 | // ay2 = ay2, b <-> y 401 | newControl.ay2 = [ 402 | ay2[0], 403 | ay2[1], 404 | ay2[2] 405 | ] 406 | 407 | // by2 = 1/2*ay2 + 1/2*by2, b <-> y 408 | newControl.by2 = [ 409 | 1/2*ay2[0] + 1/2*by2[0], 410 | 1/2*ay2[1] + 1/2*by2[1], 411 | 1/2*ay2[2] + 1/2*by2[2] 412 | ] 413 | 414 | // y3 = y3, b <-> y 415 | newControl.y3 = [ 416 | y3[0], 417 | y3[1], 418 | y3[2] 419 | ] 420 | 421 | let tcontrl = { 422 | a3: newControl.y3, 423 | a2b: newControl.ay2, 424 | ab2: newControl.a2y, 425 | b3: newControl.a3, 426 | a2y: newControl.by2, 427 | aby: newControl.aby, 428 | b2y: newControl.a2b, 429 | ay2: newControl.b2y, 430 | by2: newControl.ab2, 431 | y3: newControl.b3 432 | } 433 | 434 | return tcontrl; 435 | 436 | } 437 | 438 | function getTwoNewControls (control) { 439 | return [ 440 | getOneNewControl(control), 441 | getOneNewControl({ 442 | a3: control.b3, 443 | a2b: control.ab2, 444 | ab2: control.a2b, 445 | b3: control.a3, 446 | a2y: control.b2y, 447 | aby: control.aby, 448 | b2y: control.a2y, 449 | ay2: control.by2, 450 | by2: control.ay2, 451 | y3: control.y3 452 | }) 453 | ] 454 | } 455 | 456 | export function getBezierSurfacePath (matrix, density, anchor, d, alpha, beta, ratio) { 457 | 458 | let str = ''; 459 | 460 | let arr = []; 461 | 462 | const recursiveTime = density; 463 | 464 | const possibleDraw = (control, n) => { 465 | 466 | let controls = getTwoNewControls(control); 467 | let control1 = controls[0]; 468 | let control2 = controls[1]; 469 | 470 | if (n == recursiveTime) { 471 | 472 | let strArr = [], ret; 473 | 474 | strArr.push('M'); 475 | ret = getProjection(anchor, d, alpha, beta, ratio, control1.a3); 476 | strArr.push(ret.x); 477 | strArr.push(ret.y); 478 | 479 | strArr.push('L'); 480 | ret = getProjection(anchor, d, alpha, beta, ratio, control1.b3); 481 | strArr.push(ret.x); 482 | strArr.push(ret.y); 483 | 484 | strArr.push('L'); 485 | ret = getProjection(anchor, d, alpha, beta, ratio, control1.y3); 486 | strArr.push(ret.x); 487 | strArr.push(ret.y); 488 | 489 | strArr.push('Z'); 490 | 491 | str += strArr.join(' ') + ' '; 492 | 493 | arr.push({ 494 | d: strArr.join(' '), 495 | center: [ 496 | (control1.a3[0] + control1.b3[0] + control1.y3[0])/3, 497 | (control1.a3[1] + control1.b3[1] + control1.y3[1])/3, 498 | (control1.a3[2] + control1.b3[2] + control1.y3[2])/3, 499 | ] 500 | }) 501 | 502 | strArr = []; 503 | 504 | strArr.push('M'); 505 | ret = getProjection(anchor, d, alpha, beta, ratio, control2.b3); 506 | strArr.push(ret.x); 507 | strArr.push(ret.y); 508 | 509 | strArr.push('L'); 510 | ret = getProjection(anchor, d, alpha, beta, ratio, control2.a3); 511 | strArr.push(ret.x); 512 | strArr.push(ret.y); 513 | 514 | strArr.push('L'); 515 | ret = getProjection(anchor, d, alpha, beta, ratio, control2.y3); 516 | strArr.push(ret.x); 517 | strArr.push(ret.y); 518 | 519 | 520 | str += strArr.join(' ') + ' '; 521 | 522 | strArr.push('Z'); 523 | 524 | 525 | arr.push({ 526 | d: strArr.join(' '), 527 | center: [ 528 | (control2.a3[0] + control2.b3[0] + control2.y3[0])/3, 529 | (control2.a3[1] + control2.b3[1] + control2.y3[1])/3, 530 | (control2.a3[2] + control2.b3[2] + control2.y3[2])/3, 531 | ] 532 | }) 533 | 534 | } else { 535 | 536 | possibleDraw(control1, n + 1); 537 | possibleDraw(control2, n + 1); 538 | } 539 | 540 | } 541 | 542 | let control1 = { 543 | b3: matrix[0][0], 544 | ab2: matrix[1][0], 545 | b2y: matrix[1][1], 546 | a2b: matrix[2][0], 547 | aby: matrix[2][1], 548 | by2: matrix[2][2], 549 | a3: matrix[3][0], 550 | a2y: matrix[3][1], 551 | ay2: matrix[3][2], 552 | y3: matrix[3][3] 553 | } 554 | 555 | possibleDraw(control1, 0); 556 | 557 | let control2 = { 558 | b3: matrix[3][3], 559 | ab2: matrix[2][3], 560 | b2y: matrix[2][2], 561 | a2b: matrix[1][3], 562 | aby: matrix[1][2], 563 | by2: matrix[1][1], 564 | a3: matrix[0][3], 565 | a2y: matrix[0][2], 566 | ay2: matrix[0][1], 567 | y3: matrix[0][0] 568 | } 569 | 570 | possibleDraw(control2, 0); 571 | 572 | 573 | return arr; 574 | } 575 | 576 | export function getBezierTrianglePath (control, density, anchor, d, alpha, beta, ratio) { 577 | 578 | let arr = []; 579 | 580 | const recursiveTime = density; 581 | 582 | const possibleDraw = (control, n) => { 583 | 584 | let controls = getTwoNewControls(control); 585 | let control1 = controls[0]; 586 | let control2 = controls[1]; 587 | 588 | if (n == recursiveTime) { 589 | 590 | let strArr = [], ret; 591 | 592 | strArr.push('M'); 593 | ret = getProjection(anchor, d, alpha, beta, ratio, control1.a3); 594 | strArr.push(ret.x); 595 | strArr.push(ret.y); 596 | 597 | strArr.push('L'); 598 | ret = getProjection(anchor, d, alpha, beta, ratio, control1.b3); 599 | strArr.push(ret.x); 600 | strArr.push(ret.y); 601 | 602 | strArr.push('L'); 603 | ret = getProjection(anchor, d, alpha, beta, ratio, control1.y3); 604 | strArr.push(ret.x); 605 | strArr.push(ret.y); 606 | 607 | strArr.push('Z'); 608 | 609 | arr.push({ 610 | d: strArr.join(' '), 611 | center: [ 612 | (control1.a3[0] + control1.b3[0] + control1.y3[0])/3, 613 | (control1.a3[1] + control1.b3[1] + control1.y3[1])/3, 614 | (control1.a3[2] + control1.b3[2] + control1.y3[2])/3, 615 | ] 616 | }) 617 | 618 | strArr = []; 619 | 620 | strArr.push('M'); 621 | ret = getProjection(anchor, d, alpha, beta, ratio, control2.b3); 622 | strArr.push(ret.x); 623 | strArr.push(ret.y); 624 | 625 | strArr.push('L'); 626 | ret = getProjection(anchor, d, alpha, beta, ratio, control2.a3); 627 | strArr.push(ret.x); 628 | strArr.push(ret.y); 629 | 630 | strArr.push('L'); 631 | ret = getProjection(anchor, d, alpha, beta, ratio, control2.y3); 632 | strArr.push(ret.x); 633 | strArr.push(ret.y); 634 | 635 | strArr.push('Z'); 636 | 637 | arr.push({ 638 | d: strArr.join(' '), 639 | center: [ 640 | (control2.a3[0] + control2.b3[0] + control2.y3[0])/3, 641 | (control2.a3[1] + control2.b3[1] + control2.y3[1])/3, 642 | (control2.a3[2] + control2.b3[2] + control2.y3[2])/3, 643 | ] 644 | }) 645 | 646 | } else { 647 | 648 | possibleDraw(control1, n + 1); 649 | possibleDraw(control2, n + 1); 650 | } 651 | 652 | } 653 | 654 | possibleDraw(control, 0); 655 | 656 | return arr; 657 | 658 | } --------------------------------------------------------------------------------