├── .gitignore ├── .npmignore ├── LICENSE.md ├── README.md ├── demo └── index.js ├── function.js ├── index.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | bower_components 2 | node_modules 3 | *.log 4 | .DS_Store 5 | bundle.js 6 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | bower_components 2 | node_modules 3 | *.log 4 | .DS_Store 5 | bundle.js 6 | test 7 | test.js 8 | demo/ -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2014 Matt DesLauriers 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 17 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE 20 | OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # adaptive-quadratic-curve 2 | 3 | [![stable](http://badges.github.io/stability-badges/dist/stable.svg)](http://github.com/badges/stability-badges) 4 | 5 | 6 | Builds a quadratic curve that is adaptive; that is to say, it has more points along curved corners, and less points along straight lines. This can be used to produce scalable curves that are consistently smooth, while using a small number of steps. Based on [AntiGrain](http://antigrain.com/research/adaptive_bezier/). 7 | 8 | Also see [adaptive-bezier-curve](https://nodei.co/npm/adaptive-bezier-curve/). 9 | 10 | ```js 11 | var quadratic = require('adaptive-quadratic-curve') 12 | 13 | var start = [20, 20], 14 | c1 = [100, 159], 15 | end = [200, 20], 16 | scale = 2 17 | 18 | var points = quadratic(start, c1, end, scale) 19 | 20 | //returns a list of 2d points: [ [x,y], [x,y], [x,y] ... ] 21 | ``` 22 | 23 | See [demo/index.js](demo/index.js) for an example with HTML5 canvas. 24 | 25 | ![img](http://i.imgur.com/JByqVNI.png) 26 | 27 | ## Usage 28 | 29 | [![NPM](https://nodei.co/npm/adaptive-quadratic-curve.png)](https://nodei.co/npm/adaptive-quadratic-curve/) 30 | 31 | #### `quadratic(start, c1, end[, scale, points])` 32 | 33 | Returns an adaptive quadratic curve for the given three control points. You can specify a `scale` to produce better smoothing for scaled contexts, otherwise it defaults to 1.0. 34 | 35 | If you specify a `points` array, the new points will be pushed onto that array (useful for building paths). If you don't specify `points`, a new array will be used. 36 | 37 | ## License 38 | 39 | The AntiGrain 2.4 code is licensed under BSD-3-Clause, see [LICENSE.md](http://github.com/mattdesl/adaptive-bezier-curve/blob/master/LICENSE.md) for details. 40 | -------------------------------------------------------------------------------- /demo/index.js: -------------------------------------------------------------------------------- 1 | var adaptive = require('../') 2 | 3 | require('canvas-testbed')(function(ctx, width, height) { 4 | var scale = 2 5 | var path = adaptive([20, 20], [100, 159], [30, 100], scale) 6 | 7 | ctx.save() 8 | ctx.scale(scale, scale) 9 | 10 | ctx.beginPath() 11 | ctx.moveTo(20, 20) 12 | ctx.quadraticCurveTo(100,159, 30,100) 13 | ctx.lineWidth = 4 14 | ctx.strokeStyle = 'red' 15 | ctx.stroke() 16 | 17 | ctx.strokeStyle = '#000' 18 | path.forEach(function(p) { 19 | var r = 3 20 | ctx.fillRect(p[0]-r/2,p[1]-r/2, r, r) 21 | }) 22 | ctx.restore() 23 | }) -------------------------------------------------------------------------------- /function.js: -------------------------------------------------------------------------------- 1 | function clone(point) { //TODO: use gl-vec2 for this 2 | return [point[0], point[1]] 3 | } 4 | 5 | function vec2(x, y) { 6 | return [x, y] 7 | } 8 | 9 | module.exports = function createQuadraticBuilder(opt) { 10 | opt = opt||{} 11 | 12 | var RECURSION_LIMIT = typeof opt.recursion === 'number' ? opt.recursion : 8 13 | var FLT_EPSILON = typeof opt.epsilon === 'number' ? opt.epsilon : 1.19209290e-7 14 | var PATH_DISTANCE_EPSILON = typeof opt.pathEpsilon === 'number' ? opt.pathEpsilon : 1.0 15 | 16 | var curve_angle_tolerance_epsilon = typeof opt.angleEpsilon === 'number' ? opt.angleEpsilon : 0.01 17 | var m_angle_tolerance = opt.angleTolerance || 0 18 | 19 | return function quadraticCurve(start, c1, end, scale, points) { 20 | if (!points) 21 | points = [] 22 | 23 | scale = typeof scale === 'number' ? scale : 1.0 24 | var distanceTolerance = PATH_DISTANCE_EPSILON / scale 25 | distanceTolerance *= distanceTolerance 26 | begin(start, c1, end, points, distanceTolerance) 27 | return points 28 | } 29 | 30 | ////// Based on: 31 | ////// https://github.com/pelson/antigrain/blob/master/agg-2.4/src/agg_curves.cpp 32 | 33 | function begin(start, c1, end, points, distanceTolerance) { 34 | points.push(clone(start)) 35 | var x1 = start[0], 36 | y1 = start[1], 37 | x2 = c1[0], 38 | y2 = c1[1], 39 | x3 = end[0], 40 | y3 = end[1] 41 | recursive(x1, y1, x2, y2, x3, y3, points, distanceTolerance, 0) 42 | points.push(clone(end)) 43 | } 44 | 45 | 46 | 47 | function recursive(x1, y1, x2, y2, x3, y3, points, distanceTolerance, level) { 48 | if(level > RECURSION_LIMIT) 49 | return 50 | 51 | var pi = Math.PI 52 | 53 | // Calculate all the mid-points of the line segments 54 | //---------------------- 55 | var x12 = (x1 + x2) / 2 56 | var y12 = (y1 + y2) / 2 57 | var x23 = (x2 + x3) / 2 58 | var y23 = (y2 + y3) / 2 59 | var x123 = (x12 + x23) / 2 60 | var y123 = (y12 + y23) / 2 61 | 62 | var dx = x3-x1 63 | var dy = y3-y1 64 | var d = Math.abs(((x2 - x3) * dy - (y2 - y3) * dx)) 65 | 66 | if(d > FLT_EPSILON) 67 | { 68 | // Regular care 69 | //----------------- 70 | if(d * d <= distanceTolerance * (dx*dx + dy*dy)) 71 | { 72 | // If the curvature doesn't exceed the distance_tolerance value 73 | // we tend to finish subdivisions. 74 | //---------------------- 75 | if(m_angle_tolerance < curve_angle_tolerance_epsilon) 76 | { 77 | points.push(vec2(x123, y123)) 78 | return 79 | } 80 | 81 | // Angle & Cusp Condition 82 | //---------------------- 83 | var da = Math.abs(Math.atan2(y3 - y2, x3 - x2) - Math.atan2(y2 - y1, x2 - x1)) 84 | if(da >= pi) da = 2*pi - da 85 | 86 | if(da < m_angle_tolerance) 87 | { 88 | // Finally we can stop the recursion 89 | //---------------------- 90 | points.push(vec2(x123, y123)) 91 | return 92 | } 93 | } 94 | } 95 | else 96 | { 97 | // Collinear case 98 | //----------------- 99 | dx = x123 - (x1 + x3) / 2 100 | dy = y123 - (y1 + y3) / 2 101 | if(dx*dx + dy*dy <= distanceTolerance) 102 | { 103 | points.push(vec2(x123, y123)) 104 | return 105 | } 106 | } 107 | 108 | // Continue subdivision 109 | //---------------------- 110 | recursive(x1, y1, x12, y12, x123, y123, points, distanceTolerance, level + 1) 111 | recursive(x123, y123, x23, y23, x3, y3, points, distanceTolerance, level + 1) 112 | } 113 | } -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./function')() -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "adaptive-quadratic-curve", 3 | "version": "1.0.2", 4 | "description": "adaptive and scalable 2D quadratic curves", 5 | "main": "index.js", 6 | "license": "BSD-3-Clause", 7 | "author": { 8 | "name": "Matt DesLauriers", 9 | "email": "dave.des@gmail.com", 10 | "url": "https://github.com/mattdesl" 11 | }, 12 | "dependencies": {}, 13 | "devDependencies": { 14 | "canvas-testbed": "^1.0.2" 15 | }, 16 | "scripts": { 17 | "test": "node test.js" 18 | }, 19 | "keywords": [ 20 | "bezier", 21 | "quadratic", 22 | "curve", 23 | "adaptive", 24 | "approximate", 25 | "quadraticCurveTo", 26 | "bezierCurveTo", 27 | "line", 28 | "path", 29 | "rendering", 30 | "canvas", 31 | "draw", 32 | "drawing", 33 | "webgl", 34 | "segments" 35 | ], 36 | "repository": { 37 | "type": "git", 38 | "url": "git://github.com/mattdesl/adaptive-quadratic-curve.git" 39 | }, 40 | "homepage": "https://github.com/mattdesl/adaptive-quadratic-curve", 41 | "bugs": { 42 | "url": "https://github.com/mattdesl/adaptive-quadratic-curve/issues" 43 | } 44 | } 45 | --------------------------------------------------------------------------------