perfect-freehand
Advanced tools
| function h(e,t){return[e[0]+t[0],e[1]+t[1]]}function b(e,t){return[e[0]-t[0],e[1]-t[1]]}function J(e,t){return[t[0]-e[0],t[1]-e[1]]}function p(e,t){return[e[0]*t,e[1]*t]}function be(e,t){return[e[0]/t,e[1]/t]}function E(e){return[e[1],-e[0]]}function me(e,t){return e[0]*t[0]+e[1]*t[1]}function de(e){return Math.hypot(e[0],e[1])}function he(e){return e[0]*e[0]+e[1]*e[1]}function ee(e,t){return he(b(e,t))}function I(e){return be(e,de(e))}function W(e,t){return Math.hypot(e[1]-t[1],e[0]-t[0])}function le(e,t){return p(h(e,t),.5)}function C(e,t,n){let c=Math.sin(n),i=Math.cos(n),P=e[0]-t[0],d=e[1]-t[1],O=P*i-d*c,q=P*c+d*i;return[O+t[0],q+t[1]]}function w(e,t,n){return h(e,p(J(e,t),n))}function F(e,t){return e[0]===t[0]&&e[1]===t[1]}function ae(e,t,n){return e*(1-n)+t*n}function te(e,t,n){return Math.max(t,Math.min(n,e))}function ve(e){return Array.isArray(e[0])?e.map(([t,n,c=.5])=>[t,n,c]):e.map(({x:t,y:n,pressure:c=.5})=>[t,n,c])}function X(e,t,n,c=.5){return t?(c=te(n(c),0,1),(t<0?ae(e,e+e*te(t,-.95,-.05),c):ae(e-e*te(t,.05,.95),e,c))/2):e/2}var{min:ne,PI:N}=Math;function xe(e,t={}){let{streamline:n=.5}=t,{simulatePressure:c=!0}=t;n=n/(c?4:2);let i=ve(e);if(i.length===0)return[];i.length===1&&i.push([...h(i[0],[1,1]),i[0][2]]);let P=[{point:[i[0][0],i[0][1]],pressure:i[0][2],vector:[0,0],distance:0,runningLength:0}];for(let d=1,O=0,q=i[d],M=P[O];d<i.length;d++,q=i[d],M=P[O]){let L=w(M.point,q,1-n);if(F(M.point,L))continue;let Y=I(b(M.point,L)),S=W(L,M.point),Z=M.runningLength+S,_={point:L,pressure:q[2],vector:Y,distance:S,runningLength:Z};P.push(_),O+=1}return P}function ge(e,t={}){let{size:n=8,thinning:c=.5,smoothing:i=.5,simulatePressure:P=!0,easing:d=u=>u,start:O={},end:q={},last:M=!1}=t,{streamline:L=.5}=t;L/=2;let{cap:Y=!0,taper:S=0,easing:Z=u=>u*(2-u)}=O,{cap:_=!0,taper:R=0,easing:fe=u=>--u*u*u+1}=q,V=e.length;if(V===0)return[];let re=e[V-1].runningLength,v=[],x=[],$=e.slice(0,5).reduce((u,y)=>(u+y.pressure)/2,e[0].pressure),g=X(n,c,d,e[V-1].pressure),ue,oe=e[0].vector,j=e[0].point,G=j,l=j,m=G,Q=!0;for(let u=0;u<V-1;u++){let{pressure:y}=e[u],{point:a,vector:o,distance:f,runningLength:r}=e[u];if(u>0&&Q&&r<n/2)continue;if(Q&&(Q=!1),c){if(P){let U=ne(1,1-f/n),D=ne(1,f/n);y=ne(1,$+(U-$)*(D/2))}g=X(n,c,d,y)}else g=n/2;ue===void 0&&(ue=g);let s=r<S?Z(r/S):1,H=re-r<R?fe((re-r)/R):1;g=Math.max(.01,g*Math.min(s,H));let se=e[u+1].vector,A=me(o,se);if(A<0){let U=p(E(oe),g);for(let D=0;D<1;D+=.2)m=C(h(a,U),a,N*-D),l=C(b(a,U),a,N*D),x.push(m),v.push(l);j=l,G=m;continue}let ce=p(E(w(se,o,A)),g);l=b(a,ce),m=h(a,ce);let ie=u<2||A<.25,pe=Math.pow(Math.max((r>n?n:n/2)*i,1),2);(ie||ee(j,l)>pe)&&(v.push(w(j,l,L)),j=l),(ie||ee(G,m)>pe)&&(x.push(w(G,m,L)),G=m),$=y,oe=o}let k=e[0],B=e[V-1],z=Q||x.length<2||v.length<2;if(z&&(!(S||R)||M)){let u=0;for(let o=0;o<V;o++){let{pressure:f,runningLength:r}=e[o];if(r>n){u=X(n,c,d,f);break}}let y=b(k.point,p(E(I(J(B.point,k.point))),u||g)),a=[];for(let o=0,f=.1;o<=1;o+=f)a.push(C(y,k.point,N*2*o));return a}let K=[],T=[];if(v.length>1&&x.length>1){m=x[1];for(let r=1;r<v.length;r++)if(!F(m,v[r])){l=v[r];break}if(Y||S)if(!S&&!(R&&z)){if(!F(m,l)){let r=b(k.point,p(I(J(m,l)),W(m,l)/2));for(let s=0,H=.1;s<=1;s+=H)K.push(C(r,k.point,N*s));v.shift(),x.shift()}}else K.push(k.point);else if(!F(m,l)){let r=I(J(m,l)),s=W(m,l)/2;K.push(b(k.point,p(r,s*.95))),K.push(b(k.point,p(r,s))),K.push(h(k.point,p(r,s))),K.push(h(k.point,p(r,s*.95))),v.shift(),x.shift()}let u=v[v.length-1],y=x[x.length-1],a=le(u,y),o=t.last?B.point:w(a,B.point,.618),f=I(b(o,a));if(_||R)if(!R&&!(S&&z)){let r=h(o,p(E(f),g));for(let s=0,H=.15;s<=1;s+=H)T.push(C(r,o,N*3*s))}else T.push(o);else{let r=w(a,o,.95),s=g*.95;T.push(h(r,p(E(f),s))),T.push(h(o,p(E(f),s))),T.push(b(o,p(E(f),s))),T.push(b(r,p(E(f),s)))}}return v.concat(T,x.reverse(),K)}function ke(e,t={}){return ge(xe(e,t),t)}export{ke as default,ge as getStrokeOutlinePoints,xe as getStrokePoints}; |
| var me=Object.defineProperty;var ge=e=>me(e,"__esModule",{value:!0});var ke=(e,t)=>{ge(e);for(var n in t)me(e,n,{get:t[n],enumerable:!0})};ke(exports,{default:()=>he,getStrokeOutlinePoints:()=>de,getStrokePoints:()=>be});function h(e,t){return[e[0]+t[0],e[1]+t[1]]}function b(e,t){return[e[0]-t[0],e[1]-t[1]]}function J(e,t){return[t[0]-e[0],t[1]-e[1]]}function p(e,t){return[e[0]*t,e[1]*t]}function Pe(e,t){return[e[0]/t,e[1]/t]}function E(e){return[e[1],-e[0]]}function le(e,t){return e[0]*t[0]+e[1]*t[1]}function Se(e){return Math.hypot(e[0],e[1])}function ye(e){return e[0]*e[0]+e[1]*e[1]}function ee(e,t){return ye(b(e,t))}function I(e){return Pe(e,Se(e))}function W(e,t){return Math.hypot(e[1]-t[1],e[0]-t[0])}function ae(e,t){return p(h(e,t),.5)}function C(e,t,n){let c=Math.sin(n),i=Math.cos(n),P=e[0]-t[0],d=e[1]-t[1],O=P*i-d*c,q=P*c+d*i;return[O+t[0],q+t[1]]}function w(e,t,n){return h(e,p(J(e,t),n))}function F(e,t){return e[0]===t[0]&&e[1]===t[1]}function ve(e,t,n){return e*(1-n)+t*n}function te(e,t,n){return Math.max(t,Math.min(n,e))}function fe(e){return Array.isArray(e[0])?e.map(([t,n,c=.5])=>[t,n,c]):e.map(({x:t,y:n,pressure:c=.5})=>[t,n,c])}function X(e,t,n,c=.5){return t?(c=te(n(c),0,1),(t<0?ve(e,e+e*te(t,-.95,-.05),c):ve(e-e*te(t,.05,.95),e,c))/2):e/2}var{min:ne,PI:N}=Math;function be(e,t={}){let{streamline:n=.5}=t,{simulatePressure:c=!0}=t;n=n/(c?4:2);let i=fe(e);if(i.length===0)return[];i.length===1&&i.push([...h(i[0],[1,1]),i[0][2]]);let P=[{point:[i[0][0],i[0][1]],pressure:i[0][2],vector:[0,0],distance:0,runningLength:0}];for(let d=1,O=0,q=i[d],M=P[O];d<i.length;d++,q=i[d],M=P[O]){let L=w(M.point,q,1-n);if(F(M.point,L))continue;let Y=I(b(M.point,L)),S=W(L,M.point),Z=M.runningLength+S,_={point:L,pressure:q[2],vector:Y,distance:S,runningLength:Z};P.push(_),O+=1}return P}function de(e,t={}){let{size:n=8,thinning:c=.5,smoothing:i=.5,simulatePressure:P=!0,easing:d=u=>u,start:O={},end:q={},last:M=!1}=t,{streamline:L=.5}=t;L/=2;let{cap:Y=!0,taper:S=0,easing:Z=u=>u*(2-u)}=O,{cap:_=!0,taper:R=0,easing:xe=u=>--u*u*u+1}=q,V=e.length;if(V===0)return[];let re=e[V-1].runningLength,v=[],x=[],$=e.slice(0,5).reduce((u,y)=>(u+y.pressure)/2,e[0].pressure),g=X(n,c,d,e[V-1].pressure),ue,oe=e[0].vector,j=e[0].point,G=j,l=j,m=G,Q=!0;for(let u=0;u<V-1;u++){let{pressure:y}=e[u],{point:a,vector:o,distance:f,runningLength:r}=e[u];if(u>0&&Q&&r<n/2)continue;if(Q&&(Q=!1),c){if(P){let U=ne(1,1-f/n),D=ne(1,f/n);y=ne(1,$+(U-$)*(D/2))}g=X(n,c,d,y)}else g=n/2;ue===void 0&&(ue=g);let s=r<S?Z(r/S):1,H=re-r<R?xe((re-r)/R):1;g=Math.max(.01,g*Math.min(s,H));let se=e[u+1].vector,A=le(o,se);if(A<0){let U=p(E(oe),g);for(let D=0;D<1;D+=.2)m=C(h(a,U),a,N*-D),l=C(b(a,U),a,N*D),x.push(m),v.push(l);j=l,G=m;continue}let ce=p(E(w(se,o,A)),g);l=b(a,ce),m=h(a,ce);let ie=u<2||A<.25,pe=Math.pow(Math.max((r>n?n:n/2)*i,1),2);(ie||ee(j,l)>pe)&&(v.push(w(j,l,L)),j=l),(ie||ee(G,m)>pe)&&(x.push(w(G,m,L)),G=m),$=y,oe=o}let k=e[0],B=e[V-1],z=Q||x.length<2||v.length<2;if(z&&(!(S||R)||M)){let u=0;for(let o=0;o<V;o++){let{pressure:f,runningLength:r}=e[o];if(r>n){u=X(n,c,d,f);break}}let y=b(k.point,p(E(I(J(B.point,k.point))),u||g)),a=[];for(let o=0,f=.1;o<=1;o+=f)a.push(C(y,k.point,N*2*o));return a}let K=[],T=[];if(v.length>1&&x.length>1){m=x[1];for(let r=1;r<v.length;r++)if(!F(m,v[r])){l=v[r];break}if(Y||S)if(!S&&!(R&&z)){if(!F(m,l)){let r=b(k.point,p(I(J(m,l)),W(m,l)/2));for(let s=0,H=.1;s<=1;s+=H)K.push(C(r,k.point,N*s));v.shift(),x.shift()}}else K.push(k.point);else if(!F(m,l)){let r=I(J(m,l)),s=W(m,l)/2;K.push(b(k.point,p(r,s*.95))),K.push(b(k.point,p(r,s))),K.push(h(k.point,p(r,s))),K.push(h(k.point,p(r,s*.95))),v.shift(),x.shift()}let u=v[v.length-1],y=x[x.length-1],a=ae(u,y),o=t.last?B.point:w(a,B.point,.618),f=I(b(o,a));if(_||R)if(!R&&!(S&&z)){let r=h(o,p(E(f),g));for(let s=0,H=.15;s<=1;s+=H)T.push(C(r,o,N*3*s))}else T.push(o);else{let r=w(a,o,.95),s=g*.95;T.push(h(r,p(E(f),s))),T.push(h(o,p(E(f),s))),T.push(b(o,p(E(f),s))),T.push(b(r,p(E(f),s)))}}return v.concat(T,x.reverse(),K)}function he(e,t={}){return de(be(e,t),t)} |
+266
| #  | ||
| Draw perfect pressure-sensitive freehand strokes. | ||
| 🔗 Try out a [demo](https://perfect-freehand-example.vercel.app/). | ||
| 💰 Using this library in a commercial product? Consider [becoming a sponsor](https://github.com/sponsors/steveruizok?frequency=recurring&sponsor=steveruizok). | ||
| ## Table of Contents | ||
| - [Installation](#installation) | ||
| - [Usage](#usage) | ||
| - [Support](#support) | ||
| - [Discussion](#discussion) | ||
| - [Author](#author) | ||
| ## Installation | ||
| ```bash | ||
| npm install perfect-freehand | ||
| ``` | ||
| or | ||
| ```bash | ||
| yarn add perfect-freehand | ||
| ``` | ||
| ## Usage | ||
| This package's default export is a function that: | ||
| - accepts an array of points and an (optional) options object | ||
| - returns a stroke outline as an array of points formatted as `[x, y]` | ||
| ```js | ||
| import getStroke from 'perfect-freehand' | ||
| ``` | ||
| You may format your input points as array _or_ an object. In both cases, the value for pressure is optional (it will default to `.5`). | ||
| ```js | ||
| getStroke([ | ||
| [0, 0, 0], | ||
| [10, 5, 0.5], | ||
| [20, 8, 0.3], | ||
| ]) | ||
| getStroke([ | ||
| { x: 0, y: 0, pressure: 0 }, | ||
| { x: 10, y: 5, pressure: 0.5 }, | ||
| { x: 20, y: 8, pressure: 0.3 }, | ||
| ]) | ||
| ``` | ||
| ### Options | ||
| The options object is optional, as are each of its properties. | ||
| | Property | Type | Default | Description | | ||
| | ------------------ | -------- | ------- | ----------------------------------------------------- | | ||
| | `size` | number | 8 | The base size (diameter) of the stroke. | | ||
| | `thinning` | number | .5 | The effect of pressure on the stroke's size. | | ||
| | `smoothing` | number | .5 | How much to soften the stroke's edges. | | ||
| | `streamline` | number | .5 | How much to streamline the stroke. | | ||
| | `simulatePressure` | boolean | true | Whether to simulate pressure based on velocity. | | ||
| | `easing` | function | t => t | An easing function to apply to each point's pressure. | | ||
| | `start` | { } | | Tapering options for the start of the line. | | ||
| | `end` | { } | | Tapering options for the end of the line. | | ||
| | `last` | boolean | true | Whether the stroke is complete. | | ||
| **Note:** When the `last` property is `true`, the line's end will be drawn at the last input point, rather than slightly behind it. | ||
| The `start` and `end` options accept an object: | ||
| | Property | Type | Default | Description | | ||
| | -------- | -------- | ------- | ------------------------------------------- | | ||
| | `cap` | boolean | true | Whether to draw a cap. | | ||
| | `taper` | number | 0 | The distance to taper. | | ||
| | `easing` | function | t => t | An easing function for the tapering effect. | | ||
| **Note:** The `cap` property has no effect when `taper` is more than zero. | ||
| ```js | ||
| getStroke(myPoints, { | ||
| size: 8, | ||
| thinning: 0.5, | ||
| smoothing: 0.5, | ||
| streamline: 0.5, | ||
| easing: (t) => t * t * t, | ||
| simulatePressure: true, | ||
| last: true, | ||
| start: { | ||
| taper: 20, | ||
| easing: (t) => t * t * t, | ||
| }, | ||
| end: { | ||
| taper: 20, | ||
| easing: (t) => t * t * t, | ||
| }, | ||
| }) | ||
| ``` | ||
| > **Tip:** To create a stroke with a steady line, set the `thinning` option to `0`. | ||
| > **Tip:** To create a stroke that gets thinner with pressure instead of thicker, use a negative number for the `thinning` option. | ||
| ### Rendering | ||
| While `getStroke` returns an array of points representing the outline of a stroke, it's up to you to decide how you will render these points. | ||
| The function below will turn the points returned by `getStroke` into SVG path data. | ||
| ```js | ||
| function getSvgPathFromStroke(stroke) { | ||
| if (!stroke.length) return '' | ||
| const d = stroke.reduce( | ||
| (acc, [x0, y0], i, arr) => { | ||
| const [x1, y1] = arr[(i + 1) % arr.length] | ||
| acc.push(x0, y0, (x0 + x1) / 2, (y0 + y1) / 2) | ||
| return acc | ||
| }, | ||
| ['M', ...stroke[0], 'Q'] | ||
| ) | ||
| d.push('Z') | ||
| return d.join(' ') | ||
| } | ||
| ``` | ||
| To use this function, first use perfect-freehand to turn your input points into a stroke outline, then pass the result to `getSvgPathFromStroke`. | ||
| ```js | ||
| import getStroke from 'perfect-freehand' | ||
| const myStroke = getStroke(myInputPoints) | ||
| const pathData = getSvgPathFromStroke(myStroke) | ||
| ``` | ||
| You could then pass this string either to an [SVG path](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d) element: | ||
| ```jsx | ||
| <path d={pathData} /> | ||
| ``` | ||
| Or, if you are rendering with HTML Canvas, you can pass the result to a [`Path2D` constructor](https://developer.mozilla.org/en-US/docs/Web/API/Path2D/Path2D#using_svg_paths)). | ||
| ```js | ||
| const myPath = new Path2D(pathData) | ||
| ctx.fill(myPath) | ||
| ``` | ||
| ### Flattening | ||
| To render a stroke as a "flattened" polygon, add the [`polygon-clipping`](https://github.com/mfogel/polygon-clipping) package and use the following function together with the `getSvgPathFromStroke`. | ||
| ```js | ||
| import polygonClipping from 'polygon-clipping' | ||
| function getFlatSvgPathFromStroke(stroke) { | ||
| const poly = polygonClipping.union([stroke]) | ||
| const d = [] | ||
| for (let face of poly) { | ||
| for (let points of face) { | ||
| d.push(getSvgPathFromStroke(points)) | ||
| } | ||
| } | ||
| return d.join(' ') | ||
| } | ||
| ``` | ||
| > **Tip:** For implementations in Typescript, see the example project included in this repository. | ||
| ### Example | ||
| ```jsx | ||
| import * as React from 'react' | ||
| import getStroke from 'perfect-freehand' | ||
| import { getSvgPathFromStroke } from './utils' | ||
| export default function Example() { | ||
| const [points, setPoints] = React.useState() | ||
| function handlePointerDown(e) { | ||
| e.preventDefault() | ||
| setPoints([[e.pageX, e.pageY, e.pressure]]) | ||
| } | ||
| function handlePointerMove(e) { | ||
| if (e.buttons === 1) { | ||
| e.preventDefault() | ||
| setPoints([...points, [e.pageX, e.pageY, e.pressure]]) | ||
| } | ||
| } | ||
| return ( | ||
| <svg | ||
| onPointerDown={handlePointerDown} | ||
| onPointerMove={handlePointerMove} | ||
| style={{ touchAction: 'none' }} | ||
| > | ||
| {points && ( | ||
| <path | ||
| d={getSvgPathFromStroke( | ||
| getStroke(points, { | ||
| size: 24, | ||
| thinning: 0.5, | ||
| smoothing: 0.5, | ||
| streamline: 0.5, | ||
| }) | ||
| )} | ||
| /> | ||
| )} | ||
| </svg> | ||
| ) | ||
| } | ||
| ``` | ||
| [](https://codesandbox.io/s/perfect-freehand-example-biwyi?fontsize=14&hidenavigation=1&theme=dark) | ||
| ### Advanced Usage | ||
| #### `StrokeOptions` | ||
| A TypeScript type for the options object. | ||
| ```ts | ||
| import { StrokeOptions } from 'perfect-freehand' | ||
| ``` | ||
| For advanced usage, the library also exports smaller functions that `getStroke` uses to generate its SVG data. While you can use `getStroke`'s data to render strokes with an HTML canvas (via the Path2D element) or with SVG paths, these new functions will allow you to create paths in other rendering technologies. | ||
| #### `getStrokePoints` | ||
| ```js | ||
| import { strokePoints } from 'perfect-freehand' | ||
| const strokePoints = getStrokePoints(rawInputPoints) | ||
| ``` | ||
| Accepts an array of points (formatted either as `[x, y, pressure]` or `{ x: number, y: number, pressure: number}`) and a streamline value. Returns a set of streamlined points as `[x, y, pressure, angle, distance, lengthAtPoint]`. The path's total length will be the length of the last point in the array. | ||
| #### `getOutlinePoints` | ||
| Accepts an array of points (formatted as `[x, y, pressure, angle, distance, length]`, i.e. the output of `getStrokePoints`) and returns an array of points (`[x, y]`) defining the outline of a pressure-sensitive stroke. | ||
| ```js | ||
| import { getOutlinePoints } from 'perfect-freehand' | ||
| const outlinePoints = getOutlinePoints(strokePoints) | ||
| ``` | ||
| ## Support | ||
| Please [open an issue](https://github.com/steveruizok/perfect-freehand/issues/new) for support. | ||
| ## Discussion | ||
| Have an idea or casual question? Visit the [discussion page](https://github.com/steveruizok/perfect-freehand/discussions). | ||
| ## Author | ||
| - [@steveruizok](https://twitter.com/steveruizok) |
Sorry, the diff of this file is not supported yet
| import type { StrokeOptions, StrokePoint } from './types'; | ||
| /** | ||
| * ## getStrokePoints | ||
| * @description Get points for a stroke. | ||
| * @param points An array of points (as `[x, y, pressure]` or `{x, y, pressure}`). Pressure is optional. | ||
| * @param streamline How much to streamline the stroke. | ||
| * @param size The stroke's size. | ||
| */ | ||
| export declare function getStrokePoints<T extends number[], K extends { | ||
| x: number; | ||
| y: number; | ||
| pressure?: number; | ||
| }>(points: (T | K)[], options?: StrokeOptions): StrokePoint[]; | ||
| /** | ||
| * ## getStrokeOutlinePoints | ||
| * @description Get an array of points (as `[x, y]`) representing the outline of a stroke. | ||
| * @param points An array of points (as `[x, y, pressure]` or `{x, y, pressure}`). Pressure is optional. | ||
| * @param options An (optional) object with options. | ||
| * @param options.size The base size (diameter) of the stroke. | ||
| * @param options.thinning The effect of pressure on the stroke's size. | ||
| * @param options.smoothing How much to soften the stroke's edges. | ||
| * @param options.easing An easing function to apply to each point's pressure. | ||
| * @param options.simulatePressure Whether to simulate pressure based on velocity. | ||
| * @param options.start Tapering and easing function for the start of the line. | ||
| * @param options.end Tapering and easing function for the end of the line. | ||
| * @param options.last Whether to handle the points as a completed stroke. | ||
| */ | ||
| export declare function getStrokeOutlinePoints(points: StrokePoint[], options?: Partial<StrokeOptions>): number[][]; | ||
| /** | ||
| * ## getStroke | ||
| * @description Returns a stroke as an array of outline points. | ||
| * @param points An array of points (as `[x, y, pressure]` or `{x, y, pressure}`). Pressure is optional. | ||
| * @param options An (optional) object with options. | ||
| * @param options.size The base size (diameter) of the stroke. | ||
| * @param options.thinning The effect of pressure on the stroke's size. | ||
| * @param options.smoothing How much to soften the stroke's edges. | ||
| * @param options.easing An easing function to apply to each point's pressure. | ||
| * @param options.simulatePressure Whether to simulate pressure based on velocity. | ||
| * @param options.start Tapering and easing function for the start of the line. | ||
| * @param options.end Tapering and easing function for the end of the line. | ||
| * @param options.last Whether to handle the points as a completed stroke. | ||
| */ | ||
| export default function getStroke<T extends number[], K extends { | ||
| x: number; | ||
| y: number; | ||
| pressure?: number; | ||
| }>(points: (T | K)[], options?: StrokeOptions): number[][]; | ||
| export { StrokeOptions }; |
| export interface StrokeOptions { | ||
| size?: number; | ||
| thinning?: number; | ||
| smoothing?: number; | ||
| streamline?: number; | ||
| easing?: (pressure: number) => number; | ||
| simulatePressure?: boolean; | ||
| start?: { | ||
| cap?: boolean; | ||
| taper?: number; | ||
| easing?: (distance: number) => number; | ||
| }; | ||
| end?: { | ||
| cap?: boolean; | ||
| taper?: number; | ||
| easing?: (distance: number) => number; | ||
| }; | ||
| last?: boolean; | ||
| } | ||
| export interface StrokePoint { | ||
| point: number[]; | ||
| pressure: number; | ||
| vector: number[]; | ||
| distance: number; | ||
| runningLength: number; | ||
| } |
| export declare function lerp(y1: number, y2: number, mu: number): number; | ||
| export declare function clamp(n: number, a: number, b: number): number; | ||
| /** | ||
| * Convert an array of points to the correct format ([x, y, radius]) | ||
| * @param points | ||
| * @returns | ||
| */ | ||
| export declare function toPointsArray<T extends number[], K extends { | ||
| x: number; | ||
| y: number; | ||
| pressure?: number; | ||
| }>(points: (T | K)[]): number[][]; | ||
| /** | ||
| * Compute a radius based on the pressure. | ||
| * @param size | ||
| * @param thinning | ||
| * @param easing | ||
| * @param pressure | ||
| * @returns | ||
| */ | ||
| export declare function getStrokeRadius(size: number, thinning: number, easing: (t: number) => number, pressure?: number): number; | ||
| export declare function withoutDuplicates(pts: number[][]): number[][]; |
| /** | ||
| * Negate a vector. | ||
| * @param A | ||
| */ | ||
| export declare function neg(A: number[]): number[]; | ||
| /** | ||
| * Add vectors. | ||
| * @param A | ||
| * @param B | ||
| */ | ||
| export declare function add(A: number[], B: number[]): number[]; | ||
| /** | ||
| * Subtract vectors. | ||
| * @param A | ||
| * @param B | ||
| */ | ||
| export declare function sub(A: number[], B: number[]): number[]; | ||
| /** | ||
| * Get the vector from vectors A to B. | ||
| * @param A | ||
| * @param B | ||
| */ | ||
| export declare function vec(A: number[], B: number[]): number[]; | ||
| /** | ||
| * Vector multiplication by scalar | ||
| * @param A | ||
| * @param n | ||
| */ | ||
| export declare function mul(A: number[], n: number): number[]; | ||
| /** | ||
| * Vector division by scalar. | ||
| * @param A | ||
| * @param n | ||
| */ | ||
| export declare function div(A: number[], n: number): number[]; | ||
| /** | ||
| * Perpendicular rotation of a vector A | ||
| * @param A | ||
| */ | ||
| export declare function per(A: number[]): number[]; | ||
| /** | ||
| * Dot product | ||
| * @param A | ||
| * @param B | ||
| */ | ||
| export declare function dpr(A: number[], B: number[]): number; | ||
| /** | ||
| * Length of the vector | ||
| * @param A | ||
| */ | ||
| export declare function len(A: number[]): number; | ||
| /** | ||
| * Length of the vector squared | ||
| * @param A | ||
| */ | ||
| export declare function len2(A: number[]): number; | ||
| /** | ||
| * Dist length from A to B squared. | ||
| * @param A | ||
| * @param B | ||
| */ | ||
| export declare function dist2(A: number[], B: number[]): number; | ||
| /** | ||
| * Get normalized / unit vector. | ||
| * @param A | ||
| */ | ||
| export declare function uni(A: number[]): number[]; | ||
| /** | ||
| * Dist length from A to B | ||
| * @param A | ||
| * @param B | ||
| */ | ||
| export declare function dist(A: number[], B: number[]): number; | ||
| /** | ||
| * Mean between two vectors or mid vector between two vectors | ||
| * @param A | ||
| * @param B | ||
| */ | ||
| export declare function med(A: number[], B: number[]): number[]; | ||
| /** | ||
| * Rotate a vector around another vector by r (radians) | ||
| * @param A vector | ||
| * @param C center | ||
| * @param r rotation in radians | ||
| */ | ||
| export declare function rotAround(A: number[], C: number[], r: number): number[]; | ||
| /** | ||
| * Interpolate vector A to B with a scalar t | ||
| * @param A | ||
| * @param B | ||
| * @param t scalar | ||
| */ | ||
| export declare function lrp(A: number[], B: number[], t: number): number[]; | ||
| export declare function isLeft(p1: number[], pc: number[], p2: number[]): number; | ||
| export declare function clockwise(p1: number[], pc: number[], p2: number[]): boolean; | ||
| export declare function isEqual(a: number[], b: number[]): boolean; |
| /* eslint-disable */ | ||
| const fs = require('fs') | ||
| const esbuild = require('esbuild') | ||
| const name = process.env.npm_package_name || '' | ||
| async function main() { | ||
| try { | ||
| esbuild.buildSync({ | ||
| entryPoints: ['./src/index.ts'], | ||
| outdir: 'dist/esm', | ||
| minify: true, | ||
| bundle: true, | ||
| format: 'cjs', | ||
| target: 'es6', | ||
| tsconfig: './tsconfig.build.json', | ||
| }) | ||
| esbuild.buildSync({ | ||
| entryPoints: ['./src/index.ts'], | ||
| outdir: 'dist/cjs', | ||
| minify: true, | ||
| bundle: true, | ||
| format: 'esm', | ||
| target: 'es6', | ||
| tsconfig: './tsconfig.build.json', | ||
| }) | ||
| fs.copyFile('./README.md', './dist/README.md', (err) => { | ||
| if (err) throw err | ||
| }) | ||
| console.log(`✔ ${name}: Built package.`) | ||
| } catch (e) { | ||
| console.log(`× ${name}: Build failed due to an error.`) | ||
| console.log(e) | ||
| } | ||
| } | ||
| main() |
| /* eslint-disable */ | ||
| const esbuild = require('esbuild') | ||
| const name = process.env.npm_package_name || '' | ||
| async function main() { | ||
| esbuild.build({ | ||
| entryPoints: ['./src/index.ts'], | ||
| outdir: 'dist/esm', | ||
| minify: false, | ||
| bundle: true, | ||
| format: 'esm', | ||
| target: 'esnext', | ||
| tsconfig: './tsconfig.json', | ||
| watch: { | ||
| onRebuild(error) { | ||
| if (error) { | ||
| console.log(`× ${name}: An error in prevented the rebuild.`) | ||
| return | ||
| } | ||
| console.log(`✔ ${name}: Rebuilt.`) | ||
| }, | ||
| }, | ||
| }) | ||
| } | ||
| main() |
Sorry, the diff of this file is not supported yet
| import getStroke, { getStrokePoints } from '../' | ||
| const points = [ | ||
| [-0.20210597826090293, 0.08050271739131176], | ||
| [-0.3031589673913402, 0.12075407608699606], | ||
| [0.1463145380434412, -0.8591202445651902], | ||
| [1.371051290760846, -2.849057404891255], | ||
| [2.9834196671195343, -4.844025985054316], | ||
| [6.289603855298878, -8.341510275135875], | ||
| [9.442695949388536, -12.590252420176625], | ||
| [13.51924199643338, -18.214623492697], | ||
| [20.057515019955787, -28.52680902895719], | ||
| [26.826651531717005, -37.68290179708731], | ||
| [33.71121978759763, -48.260948181152344], | ||
| [40.653503915537925, -59.54997137318486], | ||
| [48.124645979508074, -72.19448296920109], | ||
| [55.86021701149315, -84.51673876720923], | ||
| [63.7280025274857, -97.67786666621328], | ||
| [71.16189528548196, -109.25843061571533], | ||
| [75.87884166448009, -117.04871259046638], | ||
| [81.23731485397914, -125.44385357784188], | ||
| [85.91655144872868, -133.14142407152963], | ||
| [90.25616974610344, -139.9902093183735], | ||
| [94.92597889479086, -145.91460194179547], | ||
| [98.76088346913454, -151.87679825350642], | ||
| [103.17833575630638, -158.8578964093619], | ||
| [106.8870618998923, -164.8484454872896], | ||
| [110.74142497168529, -170.3437200262535], | ||
| [113.66860650758176, -174.0913572957354], | ||
| [116.63219727552999, -177.9651759304764], | ||
| [119.11399265950408, -181.9020852478469], | ||
| [121.35489035149115, -184.87053990653214], | ||
| [122.97533919748466, -187.35476723587476], | ||
| [124.28556362048144, -189.09688090054607], | ||
| [124.94067583197986, -190.46793773288175], | ||
| [125.76823193772904, -191.15346614904956], | ||
| [126.18200999060363, -191.49623035713347], | ||
| [126.38889901704093, -191.6676124611754], | ||
| [126.4923435302596, -189.75330351319639], | ||
| [126.54406578686891, -186.79614903920685], | ||
| [126.56992691517357, -182.81757180221211], | ||
| [126.5828574793259, -177.32828318371475], | ||
| [126.58932276140203, -170.08363887446603], | ||
| [126.59255540244013, -160.9613167198417], | ||
| [125.5941717229592, -149.9001556425295], | ||
| [122.59497988321871, -131.36957510387344], | ||
| [120.09538396334844, -117.60428483454541], | ||
| [116.84558600341333, -102.2216396998814], | ||
| [113.72068702344578, -85.53031713254938], | ||
| [110.15823753346203, -67.18465584888338], | ||
| [106.37701278847013, -49.51182520705038], | ||
| [102.4864004159742, -31.675409886133878], | ||
| [99.04109422972621, -15.257202225675655], | ||
| [96.81844113660222, -0.548098395446516], | ||
| [94.70711459004022, 13.306453519668025], | ||
| [92.65145131675919, 26.733729477225324], | ||
| [91.12361968011871, 38.94736745600392], | ||
| [90.35970386179847, 49.55418644539327], | ||
| [89.47774595263834, 59.35759594008789], | ||
| [89.03676699805825, 68.75930068743526], | ||
| [88.81627752076824, 75.46015306110894], | ||
| [88.70603278212323, 81.31057924794584], | ||
| [88.6509104128007, 87.23579234136423], | ||
| [88.62334922813946, 92.69839888807348], | ||
| [88.60956863580881, 96.92970216142805], | ||
| [88.60267833964352, 100.54535379810534], | ||
| [88.59923319156084, 102.85317961644398], | ||
| [88.59751061751953, 105.0070925256133], | ||
| [88.59664933049888, 106.08404898019796], | ||
| [88.59621868698852, 106.6225272074903], | ||
| [88.59600336523337, 107.39176632113652], | ||
| [88.5958957043558, 107.77638587795957], | ||
| [88.59584187391701, 107.4686956563711], | ||
| [88.59581495869762, 104.81485054557686], | ||
| [87.59580150108795, 99.98792799017974], | ||
| [85.59579477228309, 93.57446671248124], | ||
| [83.09579140788065, 85.86773607363193], | ||
| [80.34578972567945, 77.51437075420733], | ||
| [76.47078888457884, 67.83768809449498], | ||
| [72.53328846402852, 56.499346764638744], | ||
| [67.06453825375337, 43.830176099710684], | ||
| [61.83016314861581, 31.995590767246654], | ||
| [56.712975596047016, 20.57829810101464], | ||
| [51.15438181976262, 8.869651767898631], | ||
| [46.37508493162042, -2.4846713986593727], | ||
| [41.98543648754932, -12.661832981938346], | ||
| [37.79061226551377, -21.75041377357786], | ||
| [33.693200154496, -30.79470416939762], | ||
| [30.14449409898711, -38.8168493673075], | ||
| [27.370141071232666, -44.827921966262466], | ||
| [24.982964557355444, -49.83345826573992], | ||
| [22.78937630041682, -54.83622641547868], | ||
| [19.69258217194752, -59.83761049034803], | ||
| [17.64418510771287, -63.33830252778273], | ||
| [15.619986575595561, -66.08864854650005], | ||
| [14.607887309536892, -67.96382155585871], | ||
| [13.601837676507557, -69.40140806053807], | ||
| [13.098812859992876, -70.62020131287773], | ||
| [12.84730045173555, -71.22959793904755], | ||
| [12.221544247606886, -71.53429625213244], | ||
| ] | ||
| describe('The algorithm.', () => { | ||
| it('It works with number pairs.', () => { | ||
| const stroke = getStroke([ | ||
| [0, 0], | ||
| [10, 0], | ||
| [20, 0], | ||
| [25, 5], | ||
| [30, 5], | ||
| ]) | ||
| expect(Array.isArray(stroke)).toBeTruthy() | ||
| }) | ||
| it('It works with point pairs.', () => { | ||
| const stroke = getStroke([ | ||
| { x: 0, y: 0 }, | ||
| { x: 10, y: 0 }, | ||
| { x: 20, y: 0 }, | ||
| { x: 25, y: 5 }, | ||
| { x: 30, y: 5 }, | ||
| ]) | ||
| expect(Array.isArray(stroke)).toBeTruthy() | ||
| }) | ||
| it('It produces the same result with either input.', () => { | ||
| const strokeA = getStroke([ | ||
| [0, 0], | ||
| [10, 0], | ||
| [20, 0], | ||
| [25, 5], | ||
| [30, 5], | ||
| ]) | ||
| const strokeB = getStroke([ | ||
| { x: 0, y: 0 }, | ||
| { x: 10, y: 0 }, | ||
| { x: 20, y: 0 }, | ||
| { x: 25, y: 5 }, | ||
| { x: 30, y: 5 }, | ||
| ]) | ||
| expect(JSON.stringify(strokeA) === JSON.stringify(strokeB)).toBeTruthy() | ||
| }) | ||
| it('Caps points', () => { | ||
| const stroke = getStroke([ | ||
| [10, 200], | ||
| [10, 0], | ||
| ]) | ||
| expect(stroke.length > 4).toBeTruthy() | ||
| }) | ||
| it('Succeeds on tricky points', () => { | ||
| expect(JSON.stringify(getStroke(points)).includes('null')).toBeFalsy() | ||
| }) | ||
| it('Removes any duplicates', () => { | ||
| const pointsWithDuplicates = [ | ||
| [0, 0], | ||
| [0, 0], | ||
| [0, 0], | ||
| [0, 0], | ||
| [0, 0], | ||
| [10, 10], | ||
| [10, 10], | ||
| [10, 10], | ||
| [10, 10], | ||
| [10, 10], | ||
| [100, 100], | ||
| [100, 100], | ||
| [100, 100], | ||
| [100, 100], | ||
| [100, 100], | ||
| [100, 100], | ||
| [0, 0], | ||
| [0, 0], | ||
| ] | ||
| const strokePoints = getStrokePoints(pointsWithDuplicates) | ||
| expect(getStroke(pointsWithDuplicates)).toMatchSnapshot( | ||
| 'stroke with duplicates removed' | ||
| ) | ||
| expect(strokePoints).toMatchSnapshot( | ||
| 'stroke-points with duplicates removed' | ||
| ) | ||
| }) | ||
| it('Solves a stroke with only one point.', () => { | ||
| const stroke = getStroke([[1, 1, 0]], { | ||
| size: 1, | ||
| thinning: 0.6, | ||
| smoothing: 0.5, | ||
| streamline: 0.5, | ||
| simulatePressure: true, | ||
| last: false, | ||
| }) | ||
| expect(stroke).toMatchSnapshot() | ||
| expect(Number.isNaN([0][0])).toBe(false) | ||
| }) | ||
| }) |
| { | ||
| "extends": "./tsconfig.json", | ||
| "exclude": [ | ||
| "node_modules", | ||
| "**/*.test.tsx", | ||
| "**/*.test.ts", | ||
| "**/*.spec.tsx", | ||
| "**/*.spec.ts", | ||
| "src/test", | ||
| "dist" | ||
| ] | ||
| } |
| { | ||
| "extends": "../../tsconfig.base.json", | ||
| "include": ["src"], | ||
| "exclude": ["node_modules", "dist"], | ||
| "compilerOptions": { | ||
| "rootDir": "src", | ||
| "outDir": "./dist/types", | ||
| "baseUrl": "src", | ||
| "paths": { | ||
| "+*": ["./*"] | ||
| } | ||
| } | ||
| } |
+2
-2
| MIT License | ||
| Copyright (c) 2021 Steve Ruiz | ||
| Copyright (c) 2021 Chris Hager | ||
@@ -21,2 +21,2 @@ Permission is hereby granted, free of charge, to any person obtaining a copy | ||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
| SOFTWARE. | ||
| SOFTWARE. |
+25
-47
| { | ||
| "version": "0.4.91", | ||
| "version": "0.5.0", | ||
| "name": "perfect-freehand", | ||
| "private": false, | ||
| "description": "Draw perfect pressure-sensitive freehand strokes.", | ||
| "author": { | ||
@@ -20,52 +22,28 @@ "name": "Steve Ruiz", | ||
| "license": "MIT", | ||
| "module": "dist/perfect-freehand.esm.js", | ||
| "main": "dist/index.js", | ||
| "typings": "dist/index.d.ts", | ||
| "files": [ | ||
| "dist", | ||
| "src" | ||
| ], | ||
| "engines": { | ||
| "node": ">=10" | ||
| }, | ||
| "main": "./dist/cjs/index.js", | ||
| "module": "./dist/esm/index.js", | ||
| "types": "./dist/types/index.d.ts", | ||
| "scripts": { | ||
| "start": "tsdx watch", | ||
| "build": "tsdx build", | ||
| "test": "tsdx test", | ||
| "lint": "tsdx lint", | ||
| "prepare": "tsdx build", | ||
| "size": "size-limit", | ||
| "analyze": "size-limit --why" | ||
| "start": "node scripts/dev & tsc --watch --incremental --emitDeclarationOnly --declarationMap --outDir dist/types", | ||
| "build": "yarn clean && node scripts/build && tsc --project tsconfig.build.json --emitDeclarationOnly --outDir dist/types", | ||
| "lint": "eslint src/ --ext .ts,.tsx", | ||
| "clean": "rm -rf dist", | ||
| "ts-node": "ts-node", | ||
| "docs": "typedoc --entryPoints src/index.ts" | ||
| }, | ||
| "husky": { | ||
| "hooks": { | ||
| "pre-commit": "tsdx lint" | ||
| } | ||
| }, | ||
| "prettier": { | ||
| "printWidth": 80, | ||
| "semi": false, | ||
| "singleQuote": true, | ||
| "trailingComma": "es5" | ||
| }, | ||
| "size-limit": [ | ||
| { | ||
| "path": "dist/perfect-freehand.cjs.production.min.js", | ||
| "limit": "10 KB" | ||
| }, | ||
| { | ||
| "path": "dist/perfect-freehand.esm.js", | ||
| "limit": "10 KB" | ||
| } | ||
| ], | ||
| "devDependencies": { | ||
| "@size-limit/preset-small-lib": "^4.9.2", | ||
| "husky": "^5.0.9", | ||
| "size-limit": "^4.9.2", | ||
| "tsdx": "^0.14.1", | ||
| "tslib": "^2.1.0", | ||
| "typescript": "^4.1.5" | ||
| "@types/jest": "^27.0.1", | ||
| "@types/node": "^15.0.1", | ||
| "@types/react": "^17.0.16", | ||
| "@types/react-dom": "^17.0.9", | ||
| "esbuild": "^0.12.21", | ||
| "eslint": "^7.22.0", | ||
| "react": "^17.0.2", | ||
| "react-dom": "^17.0.2", | ||
| "ts-node": "^9.1.1", | ||
| "tslib": "^2.3.0", | ||
| "typedoc": "^0.20.35", | ||
| "typescript": "^4.3.5" | ||
| }, | ||
| "peerDependencies": {}, | ||
| "dependencies": {} | ||
| "gitHead": "285dece1681b7a21bffb9309fee476c5f44495f5" | ||
| } |
+22
-19
@@ -68,6 +68,8 @@ #  | ||
| | `easing` | function | t => t | An easing function to apply to each point's pressure. | | ||
| | `start` | function | t => t | Tapering options for the start of the line. | | ||
| | `start` | { } | | Tapering options for the start of the line. | | ||
| | `end` | { } | | Tapering options for the end of the line. | | ||
| | `last` | boolean | true | Whether the stroke is complete. | | ||
| | `last` | boolean | true | Whether the stroke is complete. | | ||
| **Note:** When the `last` property is `true`, the line's end will be drawn at the last input point, rather than slightly behind it. | ||
| The `start` and `end` options accept an object: | ||
@@ -77,6 +79,7 @@ | ||
| | -------- | -------- | ------- | ------------------------------------------- | | ||
| | `taper` | boolean | 0 | The distance to taper. | | ||
| | `easing` | function | t => | An easing function for the tapering effect. | | ||
| | `cap` | boolean | true | Whether to draw a cap. | | ||
| | `taper` | number | 0 | The distance to taper. | | ||
| | `easing` | function | t => t | An easing function for the tapering effect. | | ||
| When `taper` is zero for either start or end, the library will add a rounded cap at that end of the line. | ||
| **Note:** The `cap` property has no effect when `taper` is more than zero. | ||
@@ -89,3 +92,3 @@ ```js | ||
| streamline: 0.5, | ||
| easing: t => t * t * t, | ||
| easing: (t) => t * t * t, | ||
| simulatePressure: true, | ||
@@ -95,7 +98,7 @@ last: true, | ||
| taper: 20, | ||
| easing: t => t * t * t, | ||
| easing: (t) => t * t * t, | ||
| }, | ||
| end: { | ||
| taper: 20, | ||
| easing: t => t * t * t, | ||
| easing: (t) => t * t * t, | ||
| }, | ||
@@ -117,3 +120,3 @@ }) | ||
| function getSvgPathFromStroke(stroke) { | ||
| if (!stroke.length) return "" | ||
| if (!stroke.length) return '' | ||
@@ -126,7 +129,7 @@ const d = stroke.reduce( | ||
| }, | ||
| ["M", ...stroke[0], "Q"] | ||
| ['M', ...stroke[0], 'Q'] | ||
| ) | ||
| d.push("Z") | ||
| return d.join(" ") | ||
| d.push('Z') | ||
| return d.join(' ') | ||
| } | ||
@@ -138,3 +141,3 @@ ``` | ||
| ```js | ||
| import getStroke from "perfect-freehand" | ||
| import getStroke from 'perfect-freehand' | ||
@@ -149,3 +152,3 @@ const myStroke = getStroke(myInputPoints) | ||
| ```jsx | ||
| <path d={pathData}/> | ||
| <path d={pathData} /> | ||
| ``` | ||
@@ -187,5 +190,5 @@ | ||
| ```jsx | ||
| import * as React from "react" | ||
| import getStroke from "perfect-freehand" | ||
| import { getSvgPathFromStroke } from "./utils" | ||
| import * as React from 'react' | ||
| import getStroke from 'perfect-freehand' | ||
| import { getSvgPathFromStroke } from './utils' | ||
@@ -211,3 +214,3 @@ export default function Example() { | ||
| onPointerMove={handlePointerMove} | ||
| style={{ touchAction: "none" }} | ||
| style={{ touchAction: 'none' }} | ||
| > | ||
@@ -221,3 +224,3 @@ {points && ( | ||
| smoothing: 0.5, | ||
| streamline: 0.5 | ||
| streamline: 0.5, | ||
| }) | ||
@@ -224,0 +227,0 @@ )} |
+107
-61
| import { toPointsArray, getStrokeRadius } from './utils' | ||
| import { StrokeOptions, StrokePoint } from './types' | ||
| import type { StrokeOptions, StrokePoint } from './types' | ||
| import * as vec from './vec' | ||
@@ -18,18 +18,13 @@ | ||
| >(points: (T | K)[], options = {} as StrokeOptions): StrokePoint[] { | ||
| let { simulatePressure = true, streamline = 0.5, size = 8 } = options | ||
| let { streamline = 0.5 } = options | ||
| const { simulatePressure = true } = options | ||
| streamline /= 2 | ||
| streamline = streamline / (simulatePressure ? 4 : 2) | ||
| if (!simulatePressure) { | ||
| streamline /= 2 | ||
| } | ||
| const pts = toPointsArray(points) | ||
| let len = pts.length | ||
| if (pts.length === 0) return [] | ||
| if (len === 0) return [] | ||
| if (pts.length === 1) pts.push([...vec.add(pts[0], [1, 1]), pts[0][2]]) | ||
| if (len === 1) pts.push(vec.add(pts[0], [1, 0])) | ||
| const strokePoints: StrokePoint[] = [ | ||
@@ -47,3 +42,3 @@ { | ||
| let i = 1, j = 0, curr = pts[i], prev = strokePoints[j]; | ||
| i < len; | ||
| i < pts.length; | ||
| i++, curr = pts[i], prev = strokePoints[j] | ||
@@ -55,15 +50,16 @@ ) { | ||
| const pressure = curr[2] | ||
| const vector = vec.uni(vec.vec(point, prev.point)) | ||
| const vector = vec.uni(vec.sub(prev.point, point)) | ||
| const distance = vec.dist(point, prev.point) | ||
| const runningLength = prev.runningLength + distance | ||
| strokePoints.push({ | ||
| const strokePoint = { | ||
| point, | ||
| pressure, | ||
| pressure: curr[2], | ||
| vector, | ||
| distance, | ||
| runningLength, | ||
| }) | ||
| } | ||
| strokePoints.push(strokePoint) | ||
| j += 1 // only increment j if we add an item to strokePoints | ||
@@ -83,16 +79,16 @@ } | ||
| // Update the length to the length of the strokePoints array. | ||
| len = strokePoints.length | ||
| // const len = strokePoints.length | ||
| const totalLength = strokePoints[len - 1].runningLength | ||
| // const totalLength = strokePoints[len - 1].runningLength | ||
| for (let i = len - 2; i > 1; i--) { | ||
| const { runningLength, vector } = strokePoints[i] | ||
| const dpr = vec.dpr(strokePoints[i - 1].vector, strokePoints[i].vector) | ||
| if (totalLength - runningLength > size / 2 || dpr < 0.8) { | ||
| for (let j = i; j < len; j++) { | ||
| strokePoints[j].vector = vector | ||
| } | ||
| break | ||
| } | ||
| } | ||
| // for (let i = len - 2; i > 1; i--) { | ||
| // const { runningLength, vector } = strokePoints[i] | ||
| // const dpr = vec.dpr(strokePoints[i - 1].vector, strokePoints[i].vector) | ||
| // if (totalLength - runningLength > size / 2 || dpr < 0.8) { | ||
| // for (let j = i; j < len; j++) { | ||
| // strokePoints[j].vector = vector | ||
| // } | ||
| // break | ||
| // } | ||
| // } | ||
@@ -125,3 +121,3 @@ return strokePoints | ||
| simulatePressure = true, | ||
| easing = t => t, | ||
| easing = (t) => t, | ||
| start = {}, | ||
@@ -137,9 +133,11 @@ end = {}, | ||
| const { | ||
| cap: capStart = true, | ||
| taper: taperStart = 0, | ||
| easing: taperStartEase = t => t * (2 - t), | ||
| easing: taperStartEase = (t) => t * (2 - t), | ||
| } = start | ||
| const { | ||
| cap: capEnd = true, | ||
| taper: taperEnd = 0, | ||
| easing: taperEndEase = t => --t * t * t + 1, | ||
| easing: taperEndEase = (t) => --t * t * t + 1, | ||
| } = end | ||
@@ -168,2 +166,4 @@ | ||
| let firstRadius: number | undefined = undefined | ||
| // Previous vector | ||
@@ -180,2 +180,4 @@ let prevVector = points[0].vector | ||
| let short = true | ||
| /* | ||
@@ -188,5 +190,12 @@ Find the outline's left and right points | ||
| for (let i = 1; i < len - 1; i++) { | ||
| let { point, pressure, vector, distance, runningLength } = points[i] | ||
| for (let i = 0; i < len - 1; i++) { | ||
| let { pressure } = points[i] | ||
| const { point, vector, distance, runningLength } = points[i] | ||
| if (i > 0 && short && runningLength < size / 2) { | ||
| continue | ||
| } else if (short) { | ||
| short = false | ||
| } | ||
| /* | ||
@@ -212,2 +221,6 @@ Calculate the radius | ||
| if (firstRadius === undefined) { | ||
| firstRadius = radius | ||
| } | ||
| /* | ||
@@ -231,3 +244,3 @@ Apply tapering | ||
| radius *= Math.min(ts, te) | ||
| radius = Math.max(0.01, radius * Math.min(ts, te)) | ||
@@ -262,2 +275,3 @@ /* | ||
| } | ||
| /* | ||
@@ -278,5 +292,6 @@ Add regular points | ||
| const alwaysAdd = i === 1 || dpr < 0.25 | ||
| const alwaysAdd = i < 2 || dpr < 0.25 | ||
| const minDistance = Math.pow( | ||
| (runningLength > size ? size : size / 2) * smoothing, | ||
| Math.max((runningLength > size ? size : size / 2) * smoothing, 1), | ||
| 2 | ||
@@ -311,3 +326,3 @@ ) | ||
| const lastPoint = points[len - 1] | ||
| const isVeryShort = rightPts.length < 2 || leftPts.length < 2 | ||
| const isVeryShort = short || rightPts.length < 2 || leftPts.length < 2 | ||
@@ -361,4 +376,5 @@ /* | ||
| const startCap: number[][] = [] | ||
| const endCap: number[][] = [] | ||
| if (!taperStart && !(taperEnd && isVeryShort)) { | ||
| if (leftPts.length > 1 && rightPts.length > 1) { | ||
| tr = rightPts[1] | ||
@@ -373,18 +389,36 @@ | ||
| if (!vec.isEqual(tr, tl)) { | ||
| const start = vec.sub( | ||
| firstPoint.point, | ||
| vec.mul(vec.uni(vec.vec(tr, tl)), vec.dist(tr, tl) / 2) | ||
| ) | ||
| if (capStart || taperStart) { | ||
| if (!taperStart && !(taperEnd && isVeryShort)) { | ||
| if (!vec.isEqual(tr, tl)) { | ||
| const start = vec.sub( | ||
| firstPoint.point, | ||
| vec.mul(vec.uni(vec.vec(tr, tl)), vec.dist(tr, tl) / 2) | ||
| ) | ||
| for (let t = 0, step = 0.2; t <= 1; t += step) { | ||
| startCap.push(vec.rotAround(start, firstPoint.point, PI * t)) | ||
| for (let t = 0, step = 0.1; t <= 1; t += step) { | ||
| startCap.push(vec.rotAround(start, firstPoint.point, PI * t)) | ||
| } | ||
| leftPts.shift() | ||
| rightPts.shift() | ||
| } | ||
| } else { | ||
| startCap.push(firstPoint.point) | ||
| } | ||
| } else { | ||
| if (!vec.isEqual(tr, tl)) { | ||
| const vector = vec.uni(vec.vec(tr, tl)) | ||
| const dist = vec.dist(tr, tl) / 2 | ||
| leftPts.shift() | ||
| rightPts.shift() | ||
| startCap.push(vec.sub(firstPoint.point, vec.mul(vector, dist * 0.95))) | ||
| startCap.push(vec.sub(firstPoint.point, vec.mul(vector, dist))) | ||
| startCap.push(vec.add(firstPoint.point, vec.mul(vector, dist))) | ||
| startCap.push(vec.add(firstPoint.point, vec.mul(vector, dist * 0.95))) | ||
| leftPts.shift() | ||
| rightPts.shift() | ||
| } | ||
| } | ||
| } | ||
| /* | ||
| /* | ||
| Draw an end cap | ||
@@ -399,15 +433,27 @@ | ||
| const endCap: number[][] = [] | ||
| const lastLeft = leftPts[leftPts.length - 1] | ||
| const lastRight = rightPts[rightPts.length - 1] | ||
| const mid = vec.med(lastLeft, lastRight) | ||
| const last = options.last | ||
| ? lastPoint.point | ||
| : vec.lrp(mid, lastPoint.point, 0.618) | ||
| const vector = vec.uni(vec.sub(last, mid)) | ||
| if (!taperEnd && !(taperStart && isVeryShort)) { | ||
| const start = vec.sub( | ||
| lastPoint.point, | ||
| vec.mul(vec.per(lastPoint.vector), radius) | ||
| ) | ||
| for (let t = 0, step = 0.1; t <= 1; t += step) { | ||
| endCap.push(vec.rotAround(start, lastPoint.point, PI * 3 * t)) | ||
| if (capEnd || taperEnd) { | ||
| if (!taperEnd && !(taperStart && isVeryShort)) { | ||
| const start = vec.add(last, vec.mul(vec.per(vector), radius)) | ||
| for (let t = 0, step = 0.15; t <= 1; t += step) { | ||
| endCap.push(vec.rotAround(start, last, PI * 3 * t)) | ||
| } | ||
| } else { | ||
| endCap.push(last) | ||
| } | ||
| } else { | ||
| const justBefore = vec.lrp(mid, last, 0.95) | ||
| const r = radius * 0.95 | ||
| endCap.push(vec.add(justBefore, vec.mul(vec.per(vector), r))) | ||
| endCap.push(vec.add(last, vec.mul(vec.per(vector), r))) | ||
| endCap.push(vec.sub(last, vec.mul(vec.per(vector), r))) | ||
| endCap.push(vec.sub(justBefore, vec.mul(vec.per(vector), r))) | ||
| } | ||
| } else { | ||
| endCap.push(lastPoint.point) | ||
| } | ||
@@ -414,0 +460,0 @@ |
+2
-0
@@ -9,2 +9,3 @@ export interface StrokeOptions { | ||
| start?: { | ||
| cap?: boolean | ||
| taper?: number | ||
@@ -14,2 +15,3 @@ easing?: (distance: number) => number | ||
| end?: { | ||
| cap?: boolean | ||
| taper?: number | ||
@@ -16,0 +18,0 @@ easing?: (distance: number) => number |
-135
| ## 0.4.9 | ||
| - Prevents duplicates when parsing points. | ||
| ## 0.4.8 | ||
| - Fixes bug on start caps. | ||
| - Adds simple test. | ||
| ## 0.4.6 | ||
| - Reduce the effect of streamline by half. | ||
| - If using actual pressure, reduce the effect by half again. | ||
| - Lerp between previous and next left/right point using streamline. | ||
| - Adds check for hard turns to line end alignment. | ||
| - Removes line start alignment. This still needs to be improved, but the previous solution is not the right one. | ||
| ## 0.4.5 | ||
| - Fixes typings. | ||
| - Fixes dot product check for left vs right. | ||
| ## 0.4.4 | ||
| - Fixes typings. | ||
| ## 0.4.3 | ||
| - Improves caps, corners. | ||
| - Re-writes most comments. | ||
| ## 0.4.2 | ||
| - Fixes dots. | ||
| ## 0.4.1 | ||
| - Removes default tapering. | ||
| ## 0.4.0 | ||
| - Adds `last` option. | ||
| - Adds `start` and `end` option, each with `taper` and `easing`. | ||
| - Improves cap handling. | ||
| ## 0.3.5 | ||
| - Improves caps. | ||
| ## 0.3.4 | ||
| - Fixes bug in short strokes. | ||
| ## 0.3.3 | ||
| - Adds the `easing` property. This property accepts an [easing function](https://gist.github.com/gre/1650294) that will apply to all pressure measurements (real or simulated). Defaults to a linear easing (`t => t`). | ||
| ## 0.3.2 | ||
| - Superficial changes. | ||
| ## 0.3.1 | ||
| - Improves sharp corners that aren't sharp enough for caps, but are still sharp enough to confuse the distance-checking part of the algorithm. | ||
| ## 0.3.0 | ||
| This version has breaking changes. | ||
| - Removes polygon-clipping as a dependency. The problems it solved are no longer problems but a developer might still use it separately for aesthetic reasons. | ||
| - Removes `clipPath`. | ||
| - Removes options types other than `StrokeOptions`. | ||
| - Removes `getShortStrokeOutlinePoints`. | ||
| - Removes `pressure` option. | ||
| - Removes `minSize` and `maxSize` options. | ||
| - Adds `size` and `thinning` options. | ||
| - Renames `smooth` to `smoothing`. | ||
| - Improves caps. | ||
| - Improves dots and short strokes. | ||
| - You can now use `thinning` to create strokes that shink at high pressure as well as at low pressure. This is a normalized value based on the `size` option: | ||
| - at `0` the `thinning` property will have no effect on a stroke's width. | ||
| - at `1` a stroke will reach zero width at the lowest pressure and its full width (`size`) at the highest pressure | ||
| - at `-1` a stroke will reach zero width at the highest pressure and its full width at the lowest pressure. | ||
| - Setting `thinning` to zero has the same effect as had setting the now removed `pressure` option to `false`. | ||
| - Improves code organization and comments. | ||
| ## 0.2.5 | ||
| - Improves caps for start and end. | ||
| - Improves handling of short moves. | ||
| ## 0.2.4 | ||
| - Improves sharp corners. | ||
| ## 0.2.3 | ||
| - Brings back `simulatePressure` until I have a better way of guessing. | ||
| ## 0.2.2 | ||
| - Slight fix to line starts. | ||
| ## 0.2.1 | ||
| - Fixes actual pressure sensitivity. | ||
| ## 0.2.0 | ||
| - Breaks up algorithm into smaller functions. Because `getPath` returns an SVG path data, you can use `getPath` only with the Path2D element (for HTML Canvas) or SVG paths. These new functions will allow you to create paths in other rendering technologies. | ||
| - `getStrokePoints` will apply a streamline to the given point, calculate angle, and distances and lengths. | ||
| - `getStrokeOutlinePoints` will generate an array of points defining the outline of the path. | ||
| - `getShortStrokeOutlinePoints` will generate outline points for a very short stroke. | ||
| - `clipPath` will generate a polygon (a series of faces) from the stroke. | ||
| - `getPath` remains unchanged, and will still return an SVG path. It calls each of the above functions (more or less, depending on the mark and its options). | ||
| - Adds the `clip` option to flatten the path into a single polygon. | ||
| - Removes `type` option. We'll try to use pressure if available. To turn off pressure, set `pressure` to false. | ||
| ## 0.1.3 | ||
| - Removes hidden options, uses `maxSize` for velocity calculations. | ||
| ## 0.1.2 | ||
| - Fixes bug in pressure. | ||
| ## 0.1.2 | ||
| - Fixes bug with empty input array. | ||
| ## 0.1.0 | ||
| - Hey world. |
| import { StrokeOptions, StrokePoint } from './types'; | ||
| /** | ||
| * ## getStrokePoints | ||
| * @description Get points for a stroke. | ||
| * @param points An array of points (as `[x, y, pressure]` or `{x, y, pressure}`). Pressure is optional. | ||
| * @param streamline How much to streamline the stroke. | ||
| * @param size The stroke's size. | ||
| */ | ||
| export declare function getStrokePoints<T extends number[], K extends { | ||
| x: number; | ||
| y: number; | ||
| pressure?: number; | ||
| }>(points: (T | K)[], options?: StrokeOptions): StrokePoint[]; | ||
| /** | ||
| * ## getStrokeOutlinePoints | ||
| * @description Get an array of points (as `[x, y]`) representing the outline of a stroke. | ||
| * @param points An array of points (as `[x, y, pressure]` or `{x, y, pressure}`). Pressure is optional. | ||
| * @param options An (optional) object with options. | ||
| * @param options.size The base size (diameter) of the stroke. | ||
| * @param options.thinning The effect of pressure on the stroke's size. | ||
| * @param options.smoothing How much to soften the stroke's edges. | ||
| * @param options.easing An easing function to apply to each point's pressure. | ||
| * @param options.simulatePressure Whether to simulate pressure based on velocity. | ||
| * @param options.start Tapering and easing function for the start of the line. | ||
| * @param options.end Tapering and easing function for the end of the line. | ||
| * @param options.last Whether to handle the points as a completed stroke. | ||
| */ | ||
| export declare function getStrokeOutlinePoints(points: StrokePoint[], options?: Partial<StrokeOptions>): number[][]; | ||
| /** | ||
| * ## getStroke | ||
| * @description Returns a stroke as an array of outline points. | ||
| * @param points An array of points (as `[x, y, pressure]` or `{x, y, pressure}`). Pressure is optional. | ||
| * @param options An (optional) object with options. | ||
| * @param options.size The base size (diameter) of the stroke. | ||
| * @param options.thinning The effect of pressure on the stroke's size. | ||
| * @param options.smoothing How much to soften the stroke's edges. | ||
| * @param options.easing An easing function to apply to each point's pressure. | ||
| * @param options.simulatePressure Whether to simulate pressure based on velocity. | ||
| * @param options.start Tapering and easing function for the start of the line. | ||
| * @param options.end Tapering and easing function for the end of the line. | ||
| * @param options.last Whether to handle the points as a completed stroke. | ||
| */ | ||
| export default function getStroke<T extends number[], K extends { | ||
| x: number; | ||
| y: number; | ||
| pressure?: number; | ||
| }>(points: (T | K)[], options?: StrokeOptions): number[][]; | ||
| export { StrokeOptions }; |
| 'use strict' | ||
| if (process.env.NODE_ENV === 'production') { | ||
| module.exports = require('./perfect-freehand.cjs.production.min.js') | ||
| } else { | ||
| module.exports = require('./perfect-freehand.cjs.development.js') | ||
| } |
| 'use strict'; | ||
| Object.defineProperty(exports, '__esModule', { value: true }); | ||
| /** | ||
| * Negate a vector. | ||
| * @param A | ||
| */ | ||
| /** | ||
| * Add vectors. | ||
| * @param A | ||
| * @param B | ||
| */ | ||
| function add(A, B) { | ||
| return [A[0] + B[0], A[1] + B[1]]; | ||
| } | ||
| /** | ||
| * Subtract vectors. | ||
| * @param A | ||
| * @param B | ||
| */ | ||
| function sub(A, B) { | ||
| return [A[0] - B[0], A[1] - B[1]]; | ||
| } | ||
| /** | ||
| * Get the vector from vectors A to B. | ||
| * @param A | ||
| * @param B | ||
| */ | ||
| function vec(A, B) { | ||
| // A, B as vectors get the vector from A to B | ||
| return [B[0] - A[0], B[1] - A[1]]; | ||
| } | ||
| /** | ||
| * Vector multiplication by scalar | ||
| * @param A | ||
| * @param n | ||
| */ | ||
| function mul(A, n) { | ||
| return [A[0] * n, A[1] * n]; | ||
| } | ||
| /** | ||
| * Vector division by scalar. | ||
| * @param A | ||
| * @param n | ||
| */ | ||
| function div(A, n) { | ||
| return [A[0] / n, A[1] / n]; | ||
| } | ||
| /** | ||
| * Perpendicular rotation of a vector A | ||
| * @param A | ||
| */ | ||
| function per(A) { | ||
| return [A[1], -A[0]]; | ||
| } | ||
| /** | ||
| * Dot product | ||
| * @param A | ||
| * @param B | ||
| */ | ||
| function dpr(A, B) { | ||
| return A[0] * B[0] + A[1] * B[1]; | ||
| } | ||
| /** | ||
| * Length of the vector | ||
| * @param A | ||
| */ | ||
| function len(A) { | ||
| return Math.hypot(A[0], A[1]); | ||
| } | ||
| /** | ||
| * Length of the vector squared | ||
| * @param A | ||
| */ | ||
| function len2(A) { | ||
| return A[0] * A[0] + A[1] * A[1]; | ||
| } | ||
| /** | ||
| * Dist length from A to B squared. | ||
| * @param A | ||
| * @param B | ||
| */ | ||
| function dist2(A, B) { | ||
| return len2(sub(A, B)); | ||
| } | ||
| /** | ||
| * Get normalized / unit vector. | ||
| * @param A | ||
| */ | ||
| function uni(A) { | ||
| return div(A, len(A)); | ||
| } | ||
| /** | ||
| * Dist length from A to B | ||
| * @param A | ||
| * @param B | ||
| */ | ||
| function dist(A, B) { | ||
| return Math.hypot(A[1] - B[1], A[0] - B[0]); | ||
| } | ||
| /** | ||
| * Rotate a vector around another vector by r (radians) | ||
| * @param A vector | ||
| * @param C center | ||
| * @param r rotation in radians | ||
| */ | ||
| function rotAround(A, C, r) { | ||
| var s = Math.sin(r); | ||
| var c = Math.cos(r); | ||
| var px = A[0] - C[0]; | ||
| var py = A[1] - C[1]; | ||
| var nx = px * c - py * s; | ||
| var ny = px * s + py * c; | ||
| return [nx + C[0], ny + C[1]]; | ||
| } | ||
| /** | ||
| * Interpolate vector A to B with a scalar t | ||
| * @param A | ||
| * @param B | ||
| * @param t scalar | ||
| */ | ||
| function lrp(A, B, t) { | ||
| return add(A, mul(vec(A, B), t)); | ||
| } // isLeft: >0 for counterclockwise | ||
| function isEqual(a, b) { | ||
| return a[0] === b[0] && a[1] === b[1]; | ||
| } | ||
| function lerp(y1, y2, mu) { | ||
| return y1 * (1 - mu) + y2 * mu; | ||
| } | ||
| function clamp(n, a, b) { | ||
| return Math.max(a, Math.min(b, n)); | ||
| } | ||
| /** | ||
| * Convert an array of points to the correct format ([x, y, radius]) | ||
| * @param points | ||
| * @returns | ||
| */ | ||
| function toPointsArray(points) { | ||
| if (Array.isArray(points[0])) { | ||
| return points.map(function (_ref) { | ||
| var x = _ref[0], | ||
| y = _ref[1], | ||
| _ref$ = _ref[2], | ||
| pressure = _ref$ === void 0 ? 0.5 : _ref$; | ||
| return [x, y, pressure]; | ||
| }); | ||
| } else { | ||
| return points.map(function (_ref2) { | ||
| var x = _ref2.x, | ||
| y = _ref2.y, | ||
| _ref2$pressure = _ref2.pressure, | ||
| pressure = _ref2$pressure === void 0 ? 0.5 : _ref2$pressure; | ||
| return [x, y, pressure]; | ||
| }); | ||
| } | ||
| } | ||
| /** | ||
| * Compute a radius based on the pressure. | ||
| * @param size | ||
| * @param thinning | ||
| * @param easing | ||
| * @param pressure | ||
| * @returns | ||
| */ | ||
| function getStrokeRadius(size, thinning, easing, pressure) { | ||
| if (pressure === void 0) { | ||
| pressure = 0.5; | ||
| } | ||
| if (!thinning) return size / 2; | ||
| pressure = clamp(easing(pressure), 0, 1); | ||
| return (thinning < 0 ? lerp(size, size + size * clamp(thinning, -0.95, -0.05), pressure) : lerp(size - size * clamp(thinning, 0.05, 0.95), size, pressure)) / 2; | ||
| } | ||
| var min = Math.min, | ||
| PI = Math.PI; | ||
| /** | ||
| * ## getStrokePoints | ||
| * @description Get points for a stroke. | ||
| * @param points An array of points (as `[x, y, pressure]` or `{x, y, pressure}`). Pressure is optional. | ||
| * @param streamline How much to streamline the stroke. | ||
| * @param size The stroke's size. | ||
| */ | ||
| function getStrokePoints(points, options) { | ||
| if (options === void 0) { | ||
| options = {}; | ||
| } | ||
| var _options = options, | ||
| _options$simulatePres = _options.simulatePressure, | ||
| simulatePressure = _options$simulatePres === void 0 ? true : _options$simulatePres, | ||
| _options$streamline = _options.streamline, | ||
| streamline = _options$streamline === void 0 ? 0.5 : _options$streamline, | ||
| _options$size = _options.size, | ||
| size = _options$size === void 0 ? 8 : _options$size; | ||
| streamline /= 2; | ||
| if (!simulatePressure) { | ||
| streamline /= 2; | ||
| } | ||
| var pts = toPointsArray(points); | ||
| var len = pts.length; | ||
| if (len === 0) return []; | ||
| if (len === 1) pts.push(add(pts[0], [1, 0])); | ||
| var strokePoints = [{ | ||
| point: [pts[0][0], pts[0][1]], | ||
| pressure: pts[0][2], | ||
| vector: [0, 0], | ||
| distance: 0, | ||
| runningLength: 0 | ||
| }]; | ||
| for (var i = 1, j = 0, curr = pts[i], prev = strokePoints[j]; i < len; i++, curr = pts[i], prev = strokePoints[j]) { | ||
| var point = lrp(prev.point, curr, 1 - streamline); | ||
| if (isEqual(prev.point, point)) continue; | ||
| var pressure = curr[2]; | ||
| var vector = uni(vec(point, prev.point)); | ||
| var distance = dist(point, prev.point); | ||
| var runningLength = prev.runningLength + distance; | ||
| strokePoints.push({ | ||
| point: point, | ||
| pressure: pressure, | ||
| vector: vector, | ||
| distance: distance, | ||
| runningLength: runningLength | ||
| }); | ||
| j += 1; // only increment j if we add an item to strokePoints | ||
| } | ||
| /* | ||
| Align vectors at the end of the line | ||
| Starting from the last point, work back until we've traveled more than | ||
| half of the line's size (width). Take the current point's vector and then | ||
| work forward, setting all remaining points' vectors to this vector. This | ||
| removes the "noise" at the end of the line and allows for a better-facing | ||
| end cap. | ||
| */ | ||
| // Update the length to the length of the strokePoints array. | ||
| len = strokePoints.length; | ||
| var totalLength = strokePoints[len - 1].runningLength; | ||
| for (var _i = len - 2; _i > 1; _i--) { | ||
| var _strokePoints$_i = strokePoints[_i], | ||
| _runningLength = _strokePoints$_i.runningLength, | ||
| _vector = _strokePoints$_i.vector; | ||
| var dpr$1 = dpr(strokePoints[_i - 1].vector, strokePoints[_i].vector); | ||
| if (totalLength - _runningLength > size / 2 || dpr$1 < 0.8) { | ||
| for (var _j = _i; _j < len; _j++) { | ||
| strokePoints[_j].vector = _vector; | ||
| } | ||
| break; | ||
| } | ||
| } | ||
| return strokePoints; | ||
| } | ||
| /** | ||
| * ## getStrokeOutlinePoints | ||
| * @description Get an array of points (as `[x, y]`) representing the outline of a stroke. | ||
| * @param points An array of points (as `[x, y, pressure]` or `{x, y, pressure}`). Pressure is optional. | ||
| * @param options An (optional) object with options. | ||
| * @param options.size The base size (diameter) of the stroke. | ||
| * @param options.thinning The effect of pressure on the stroke's size. | ||
| * @param options.smoothing How much to soften the stroke's edges. | ||
| * @param options.easing An easing function to apply to each point's pressure. | ||
| * @param options.simulatePressure Whether to simulate pressure based on velocity. | ||
| * @param options.start Tapering and easing function for the start of the line. | ||
| * @param options.end Tapering and easing function for the end of the line. | ||
| * @param options.last Whether to handle the points as a completed stroke. | ||
| */ | ||
| function getStrokeOutlinePoints(points, options) { | ||
| if (options === void 0) { | ||
| options = {}; | ||
| } | ||
| var _options2 = options, | ||
| _options2$size = _options2.size, | ||
| size = _options2$size === void 0 ? 8 : _options2$size, | ||
| _options2$thinning = _options2.thinning, | ||
| thinning = _options2$thinning === void 0 ? 0.5 : _options2$thinning, | ||
| _options2$smoothing = _options2.smoothing, | ||
| smoothing = _options2$smoothing === void 0 ? 0.5 : _options2$smoothing, | ||
| _options2$simulatePre = _options2.simulatePressure, | ||
| simulatePressure = _options2$simulatePre === void 0 ? true : _options2$simulatePre, | ||
| _options2$easing = _options2.easing, | ||
| easing = _options2$easing === void 0 ? function (t) { | ||
| return t; | ||
| } : _options2$easing, | ||
| _options2$start = _options2.start, | ||
| start = _options2$start === void 0 ? {} : _options2$start, | ||
| _options2$end = _options2.end, | ||
| end = _options2$end === void 0 ? {} : _options2$end, | ||
| _options2$last = _options2.last, | ||
| isComplete = _options2$last === void 0 ? false : _options2$last; | ||
| var _options3 = options, | ||
| _options3$streamline = _options3.streamline, | ||
| streamline = _options3$streamline === void 0 ? 0.5 : _options3$streamline; | ||
| streamline /= 2; | ||
| var _start$taper = start.taper, | ||
| taperStart = _start$taper === void 0 ? 0 : _start$taper, | ||
| _start$easing = start.easing, | ||
| taperStartEase = _start$easing === void 0 ? function (t) { | ||
| return t * (2 - t); | ||
| } : _start$easing; | ||
| var _end$taper = end.taper, | ||
| taperEnd = _end$taper === void 0 ? 0 : _end$taper, | ||
| _end$easing = end.easing, | ||
| taperEndEase = _end$easing === void 0 ? function (t) { | ||
| return --t * t * t + 1; | ||
| } : _end$easing; // The number of points in the array | ||
| var len = points.length; // We can't do anything with an empty array. | ||
| if (len === 0) return []; // The total length of the line | ||
| var totalLength = points[len - 1].runningLength; // Our collected left and right points | ||
| var leftPts = []; | ||
| var rightPts = []; // Previous pressure (start with average of first five pressures) | ||
| var prevPressure = points.slice(0, 5).reduce(function (acc, cur) { | ||
| return (acc + cur.pressure) / 2; | ||
| }, points[0].pressure); // The current radius | ||
| var radius = getStrokeRadius(size, thinning, easing, points[len - 1].pressure); // Previous vector | ||
| var prevVector = points[0].vector; // Previous left and right points | ||
| var pl = points[0].point; | ||
| var pr = pl; // Temporary left and right points | ||
| var tl = pl; | ||
| var tr = pr; | ||
| /* | ||
| Find the outline's left and right points | ||
| Iterating through the points and populate the rightPts and leftPts arrays, | ||
| skipping the first and last pointsm, which will get caps later on. | ||
| */ | ||
| for (var i = 1; i < len - 1; i++) { | ||
| var _points$i = points[i], | ||
| point = _points$i.point, | ||
| pressure = _points$i.pressure, | ||
| vector = _points$i.vector, | ||
| distance = _points$i.distance, | ||
| runningLength = _points$i.runningLength; | ||
| /* | ||
| Calculate the radius | ||
| If not thinning, the current point's radius will be half the size; or | ||
| otherwise, the size will be based on the current (real or simulated) | ||
| pressure. | ||
| */ | ||
| if (thinning) { | ||
| if (simulatePressure) { | ||
| var rp = min(1, 1 - distance / size); | ||
| var sp = min(1, distance / size); | ||
| pressure = min(1, prevPressure + (rp - prevPressure) * (sp / 2)); | ||
| } | ||
| radius = getStrokeRadius(size, thinning, easing, pressure); | ||
| } else { | ||
| radius = size / 2; | ||
| } | ||
| /* | ||
| Apply tapering | ||
| If the current length is within the taper distance at either the | ||
| start or the end, calculate the taper strengths. Apply the smaller | ||
| of the two taper strengths to the radius. | ||
| */ | ||
| var ts = runningLength < taperStart ? taperStartEase(runningLength / taperStart) : 1; | ||
| var te = totalLength - runningLength < taperEnd ? taperEndEase((totalLength - runningLength) / taperEnd) : 1; | ||
| radius *= Math.min(ts, te); | ||
| /* | ||
| Handle sharp corners | ||
| Find the difference (dot product) between the current and next vector. | ||
| If the next vector is at more than a right angle to the current vector, | ||
| draw a cap at the current point. | ||
| */ | ||
| var nextVector = points[i + 1].vector; | ||
| var dpr$1 = dpr(vector, nextVector); | ||
| if (dpr$1 < 0) { | ||
| var _offset = mul(per(prevVector), radius); | ||
| for (var t = 0; t < 1; t += 0.2) { | ||
| tr = rotAround(add(point, _offset), point, PI * -t); | ||
| tl = rotAround(sub(point, _offset), point, PI * t); | ||
| rightPts.push(tr); | ||
| leftPts.push(tl); | ||
| } | ||
| pl = tl; | ||
| pr = tr; | ||
| continue; | ||
| } | ||
| /* | ||
| Add regular points | ||
| Project points to either side of the current point, using the | ||
| calculated size as a distance. If a point's distance to the | ||
| previous point on that side greater than the minimum distance | ||
| (or if the corner is kinda sharp), add the points to the side's | ||
| points array. | ||
| */ | ||
| var offset = mul(per(lrp(nextVector, vector, dpr$1)), radius); | ||
| tl = sub(point, offset); | ||
| tr = add(point, offset); | ||
| var alwaysAdd = i === 1 || dpr$1 < 0.25; | ||
| var minDistance = Math.pow((runningLength > size ? size : size / 2) * smoothing, 2); | ||
| if (alwaysAdd || dist2(pl, tl) > minDistance) { | ||
| leftPts.push(lrp(pl, tl, streamline)); | ||
| pl = tl; | ||
| } | ||
| if (alwaysAdd || dist2(pr, tr) > minDistance) { | ||
| rightPts.push(lrp(pr, tr, streamline)); | ||
| pr = tr; | ||
| } // Set variables for next iteration | ||
| prevPressure = pressure; | ||
| prevVector = vector; | ||
| } | ||
| /* | ||
| Drawing caps | ||
| Now that we have our points on either side of the line, we need to | ||
| draw caps at the start and end. Tapered lines don't have caps, but | ||
| may have dots for very short lines. | ||
| */ | ||
| var firstPoint = points[0]; | ||
| var lastPoint = points[len - 1]; | ||
| var isVeryShort = rightPts.length < 2 || leftPts.length < 2; | ||
| /* | ||
| Draw a dot for very short or completed strokes | ||
| If the line is too short to gather left or right points and if the line is | ||
| not tapered on either side, draw a dot. If the line is tapered, then only | ||
| draw a dot if the line is both very short and complete. If we draw a dot, | ||
| we can just return those points. | ||
| */ | ||
| if (isVeryShort && (!(taperStart || taperEnd) || isComplete)) { | ||
| var ir = 0; | ||
| for (var _i2 = 0; _i2 < len; _i2++) { | ||
| var _points$_i = points[_i2], | ||
| _pressure = _points$_i.pressure, | ||
| _runningLength2 = _points$_i.runningLength; | ||
| if (_runningLength2 > size) { | ||
| ir = getStrokeRadius(size, thinning, easing, _pressure); | ||
| break; | ||
| } | ||
| } | ||
| var _start = sub(firstPoint.point, mul(per(uni(vec(lastPoint.point, firstPoint.point))), ir || radius)); | ||
| var dotPts = []; | ||
| for (var _t = 0, step = 0.1; _t <= 1; _t += step) { | ||
| dotPts.push(rotAround(_start, firstPoint.point, PI * 2 * _t)); | ||
| } | ||
| return dotPts; | ||
| } | ||
| /* | ||
| Draw a start cap | ||
| Unless the line has a tapered start, or unless the line has a tapered end | ||
| and the line is very short, draw a start cap around the first point. Use | ||
| the distance between the second left and right point for the cap's radius. | ||
| Finally remove the first left and right points. :psyduck: | ||
| */ | ||
| var startCap = []; | ||
| if (!taperStart && !(taperEnd && isVeryShort)) { | ||
| tr = rightPts[1]; | ||
| for (var _i3 = 1; _i3 < leftPts.length; _i3++) { | ||
| if (!isEqual(tr, leftPts[_i3])) { | ||
| tl = leftPts[_i3]; | ||
| break; | ||
| } | ||
| } | ||
| if (!isEqual(tr, tl)) { | ||
| var _start2 = sub(firstPoint.point, mul(uni(vec(tr, tl)), dist(tr, tl) / 2)); | ||
| for (var _t2 = 0, _step = 0.2; _t2 <= 1; _t2 += _step) { | ||
| startCap.push(rotAround(_start2, firstPoint.point, PI * _t2)); | ||
| } | ||
| leftPts.shift(); | ||
| rightPts.shift(); | ||
| } | ||
| } | ||
| /* | ||
| Draw an end cap | ||
| If the line does not have a tapered end, and unless the line has a tapered | ||
| start and the line is very short, draw a cap around the last point. Finally, | ||
| remove the last left and right points. Otherwise, add the last point. Note | ||
| that This cap is a full-turn-and-a-half: this prevents incorrect caps on | ||
| sharp end turns. | ||
| */ | ||
| var endCap = []; | ||
| if (!taperEnd && !(taperStart && isVeryShort)) { | ||
| var _start3 = sub(lastPoint.point, mul(per(lastPoint.vector), radius)); | ||
| for (var _t3 = 0, _step2 = 0.1; _t3 <= 1; _t3 += _step2) { | ||
| endCap.push(rotAround(_start3, lastPoint.point, PI * 3 * _t3)); | ||
| } | ||
| } else { | ||
| endCap.push(lastPoint.point); | ||
| } | ||
| /* | ||
| Return the points in the correct windind order: begin on the left side, then | ||
| continue around the end cap, then come back along the right side, and finally | ||
| complete the start cap. | ||
| */ | ||
| return leftPts.concat(endCap, rightPts.reverse(), startCap); | ||
| } | ||
| /** | ||
| * ## getStroke | ||
| * @description Returns a stroke as an array of outline points. | ||
| * @param points An array of points (as `[x, y, pressure]` or `{x, y, pressure}`). Pressure is optional. | ||
| * @param options An (optional) object with options. | ||
| * @param options.size The base size (diameter) of the stroke. | ||
| * @param options.thinning The effect of pressure on the stroke's size. | ||
| * @param options.smoothing How much to soften the stroke's edges. | ||
| * @param options.easing An easing function to apply to each point's pressure. | ||
| * @param options.simulatePressure Whether to simulate pressure based on velocity. | ||
| * @param options.start Tapering and easing function for the start of the line. | ||
| * @param options.end Tapering and easing function for the end of the line. | ||
| * @param options.last Whether to handle the points as a completed stroke. | ||
| */ | ||
| function getStroke(points, options) { | ||
| if (options === void 0) { | ||
| options = {}; | ||
| } | ||
| return getStrokeOutlinePoints(getStrokePoints(points, options), options); | ||
| } | ||
| exports.default = getStroke; | ||
| exports.getStrokeOutlinePoints = getStrokeOutlinePoints; | ||
| exports.getStrokePoints = getStrokePoints; | ||
| //# sourceMappingURL=perfect-freehand.cjs.development.js.map |
| {"version":3,"file":"perfect-freehand.cjs.development.js","sources":["../src/vec.ts","../src/utils.ts","../src/index.ts"],"sourcesContent":["/**\n * Negate a vector.\n * @param A\n */\nexport function neg(A: number[]) {\n return [-A[0], -A[1]]\n}\n\n/**\n * Add vectors.\n * @param A\n * @param B\n */\nexport function add(A: number[], B: number[]) {\n return [A[0] + B[0], A[1] + B[1]]\n}\n\n/**\n * Subtract vectors.\n * @param A\n * @param B\n */\nexport function sub(A: number[], B: number[]) {\n return [A[0] - B[0], A[1] - B[1]]\n}\n\n/**\n * Get the vector from vectors A to B.\n * @param A\n * @param B\n */\nexport function vec(A: number[], B: number[]) {\n // A, B as vectors get the vector from A to B\n return [B[0] - A[0], B[1] - A[1]]\n}\n\n/**\n * Vector multiplication by scalar\n * @param A\n * @param n\n */\nexport function mul(A: number[], n: number) {\n return [A[0] * n, A[1] * n]\n}\n\n/**\n * Vector division by scalar.\n * @param A\n * @param n\n */\nexport function div(A: number[], n: number) {\n return [A[0] / n, A[1] / n]\n}\n\n/**\n * Perpendicular rotation of a vector A\n * @param A\n */\nexport function per(A: number[]) {\n return [A[1], -A[0]]\n}\n\n/**\n * Dot product\n * @param A\n * @param B\n */\nexport function dpr(A: number[], B: number[]) {\n return A[0] * B[0] + A[1] * B[1]\n}\n\n/**\n * Length of the vector\n * @param A\n */\nexport function len(A: number[]) {\n return Math.hypot(A[0], A[1])\n}\n\n/**\n * Length of the vector squared\n * @param A\n */\nexport function len2(A: number[]) {\n return A[0] * A[0] + A[1] * A[1]\n}\n\n/**\n * Dist length from A to B squared.\n * @param A\n * @param B\n */\nexport function dist2(A: number[], B: number[]) {\n return len2(sub(A, B))\n}\n\n/**\n * Get normalized / unit vector.\n * @param A\n */\nexport function uni(A: number[]) {\n return div(A, len(A))\n}\n\n/**\n * Dist length from A to B\n * @param A\n * @param B\n */\nexport function dist(A: number[], B: number[]) {\n return Math.hypot(A[1] - B[1], A[0] - B[0])\n}\n\n/**\n * Mean between two vectors or mid vector between two vectors\n * @param A\n * @param B\n */\nexport function med(A: number[], B: number[]) {\n return mul(add(A, B), 0.5)\n}\n\n/**\n * Rotate a vector around another vector by r (radians)\n * @param A vector\n * @param C center\n * @param r rotation in radians\n */\nexport function rotAround(A: number[], C: number[], r: number) {\n const s = Math.sin(r)\n const c = Math.cos(r)\n\n const px = A[0] - C[0]\n const py = A[1] - C[1]\n\n const nx = px * c - py * s\n const ny = px * s + py * c\n\n return [nx + C[0], ny + C[1]]\n}\n\n/**\n * Interpolate vector A to B with a scalar t\n * @param A\n * @param B\n * @param t scalar\n */\nexport function lrp(A: number[], B: number[], t: number) {\n return add(A, mul(vec(A, B), t))\n}\n\n// isLeft: >0 for counterclockwise\n// =0 for none (degenerate)\n// <0 for clockwise\nexport function isLeft(p1: number[], pc: number[], p2: number[]) {\n return (pc[0] - p1[0]) * (p2[1] - p1[1]) - (p2[0] - p1[0]) * (pc[1] - p1[1])\n}\n\nexport function clockwise(p1: number[], pc: number[], p2: number[]) {\n return isLeft(p1, pc, p2) > 0\n}\n\nexport function isEqual(a: number[], b: number[]) {\n return a[0] === b[0] && a[1] === b[1]\n}\n","import { isEqual } from './vec'\n\nexport function lerp(y1: number, y2: number, mu: number) {\n return y1 * (1 - mu) + y2 * mu\n}\n\nexport function clamp(n: number, a: number, b: number) {\n return Math.max(a, Math.min(b, n))\n}\n\n/**\n * Convert an array of points to the correct format ([x, y, radius])\n * @param points\n * @returns\n */\nexport function toPointsArray<\n T extends number[],\n K extends { x: number; y: number; pressure?: number }\n>(points: (T | K)[]): number[][] {\n if (Array.isArray(points[0])) {\n return (points as number[][]).map(([x, y, pressure = 0.5]) => [\n x,\n y,\n pressure,\n ])\n } else {\n return (points as {\n x: number\n y: number\n pressure?: number\n }[]).map(({ x, y, pressure = 0.5 }) => [x, y, pressure])\n }\n}\n\n/**\n * Compute a radius based on the pressure.\n * @param size\n * @param thinning\n * @param easing\n * @param pressure\n * @returns\n */\nexport function getStrokeRadius(\n size: number,\n thinning: number,\n easing: (t: number) => number,\n pressure = 0.5\n) {\n if (!thinning) return size / 2\n pressure = clamp(easing(pressure), 0, 1)\n return (\n (thinning < 0\n ? lerp(size, size + size * clamp(thinning, -0.95, -0.05), pressure)\n : lerp(size - size * clamp(thinning, 0.05, 0.95), size, pressure)) / 2\n )\n}\n\nexport function withoutDuplicates(pts: number[][]) {\n const unique: number[][] = []\n\n let prev: number[] | undefined = undefined\n\n for (let pt of pts) {\n if (prev && isEqual(prev, pt)) continue\n unique.push(pt)\n prev = pt\n }\n\n return pts\n}\n","import { toPointsArray, getStrokeRadius } from './utils'\nimport { StrokeOptions, StrokePoint } from './types'\nimport * as vec from './vec'\n\nconst { min, PI } = Math\n\n/**\n * ## getStrokePoints\n * @description Get points for a stroke.\n * @param points An array of points (as `[x, y, pressure]` or `{x, y, pressure}`). Pressure is optional.\n * @param streamline How much to streamline the stroke.\n * @param size The stroke's size.\n */\nexport function getStrokePoints<\n T extends number[],\n K extends { x: number; y: number; pressure?: number }\n>(points: (T | K)[], options = {} as StrokeOptions): StrokePoint[] {\n let { simulatePressure = true, streamline = 0.5, size = 8 } = options\n\n streamline /= 2\n\n if (!simulatePressure) {\n streamline /= 2\n }\n\n const pts = toPointsArray(points)\n\n let len = pts.length\n\n if (len === 0) return []\n\n if (len === 1) pts.push(vec.add(pts[0], [1, 0]))\n\n const strokePoints: StrokePoint[] = [\n {\n point: [pts[0][0], pts[0][1]],\n pressure: pts[0][2],\n vector: [0, 0],\n distance: 0,\n runningLength: 0,\n },\n ]\n\n for (\n let i = 1, j = 0, curr = pts[i], prev = strokePoints[j];\n i < len;\n i++, curr = pts[i], prev = strokePoints[j]\n ) {\n const point = vec.lrp(prev.point, curr, 1 - streamline)\n\n if (vec.isEqual(prev.point, point)) continue\n\n const pressure = curr[2]\n const vector = vec.uni(vec.vec(point, prev.point))\n const distance = vec.dist(point, prev.point)\n const runningLength = prev.runningLength + distance\n\n strokePoints.push({\n point,\n pressure,\n vector,\n distance,\n runningLength,\n })\n\n j += 1 // only increment j if we add an item to strokePoints\n }\n\n /* \n Align vectors at the end of the line\n\n Starting from the last point, work back until we've traveled more than\n half of the line's size (width). Take the current point's vector and then\n work forward, setting all remaining points' vectors to this vector. This\n removes the \"noise\" at the end of the line and allows for a better-facing\n end cap.\n */\n\n // Update the length to the length of the strokePoints array.\n len = strokePoints.length\n\n const totalLength = strokePoints[len - 1].runningLength\n\n for (let i = len - 2; i > 1; i--) {\n const { runningLength, vector } = strokePoints[i]\n const dpr = vec.dpr(strokePoints[i - 1].vector, strokePoints[i].vector)\n if (totalLength - runningLength > size / 2 || dpr < 0.8) {\n for (let j = i; j < len; j++) {\n strokePoints[j].vector = vector\n }\n break\n }\n }\n\n return strokePoints\n}\n\n/**\n * ## getStrokeOutlinePoints\n * @description Get an array of points (as `[x, y]`) representing the outline of a stroke.\n * @param points An array of points (as `[x, y, pressure]` or `{x, y, pressure}`). Pressure is optional.\n * @param options An (optional) object with options.\n * @param options.size\tThe base size (diameter) of the stroke.\n * @param options.thinning The effect of pressure on the stroke's size.\n * @param options.smoothing\tHow much to soften the stroke's edges.\n * @param options.easing\tAn easing function to apply to each point's pressure.\n * @param options.simulatePressure Whether to simulate pressure based on velocity.\n * @param options.start Tapering and easing function for the start of the line.\n * @param options.end Tapering and easing function for the end of the line.\n * @param options.last Whether to handle the points as a completed stroke.\n */\nexport function getStrokeOutlinePoints(\n points: StrokePoint[],\n options: Partial<StrokeOptions> = {} as Partial<StrokeOptions>\n): number[][] {\n const {\n size = 8,\n thinning = 0.5,\n smoothing = 0.5,\n simulatePressure = true,\n easing = t => t,\n start = {},\n end = {},\n last: isComplete = false,\n } = options\n\n let { streamline = 0.5 } = options\n\n streamline /= 2\n\n const {\n taper: taperStart = 0,\n easing: taperStartEase = t => t * (2 - t),\n } = start\n\n const {\n taper: taperEnd = 0,\n easing: taperEndEase = t => --t * t * t + 1,\n } = end\n\n // The number of points in the array\n const len = points.length\n\n // We can't do anything with an empty array.\n if (len === 0) return []\n\n // The total length of the line\n const totalLength = points[len - 1].runningLength\n\n // Our collected left and right points\n const leftPts: number[][] = []\n const rightPts: number[][] = []\n\n // Previous pressure (start with average of first five pressures)\n let prevPressure = points\n .slice(0, 5)\n .reduce((acc, cur) => (acc + cur.pressure) / 2, points[0].pressure)\n\n // The current radius\n let radius = getStrokeRadius(size, thinning, easing, points[len - 1].pressure)\n\n // Previous vector\n let prevVector = points[0].vector\n\n // Previous left and right points\n let pl = points[0].point\n let pr = pl\n\n // Temporary left and right points\n let tl = pl\n let tr = pr\n\n /*\n Find the outline's left and right points\n\n Iterating through the points and populate the rightPts and leftPts arrays,\n skipping the first and last pointsm, which will get caps later on.\n */\n\n for (let i = 1; i < len - 1; i++) {\n let { point, pressure, vector, distance, runningLength } = points[i]\n\n /*\n Calculate the radius\n\n If not thinning, the current point's radius will be half the size; or\n otherwise, the size will be based on the current (real or simulated)\n pressure. \n */\n\n if (thinning) {\n if (simulatePressure) {\n const rp = min(1, 1 - distance / size)\n const sp = min(1, distance / size)\n pressure = min(1, prevPressure + (rp - prevPressure) * (sp / 2))\n }\n\n radius = getStrokeRadius(size, thinning, easing, pressure)\n } else {\n radius = size / 2\n }\n\n /*\n Apply tapering\n\n If the current length is within the taper distance at either the\n start or the end, calculate the taper strengths. Apply the smaller \n of the two taper strengths to the radius.\n */\n\n const ts =\n runningLength < taperStart\n ? taperStartEase(runningLength / taperStart)\n : 1\n\n const te =\n totalLength - runningLength < taperEnd\n ? taperEndEase((totalLength - runningLength) / taperEnd)\n : 1\n\n radius *= Math.min(ts, te)\n\n /*\n Handle sharp corners\n\n Find the difference (dot product) between the current and next vector.\n If the next vector is at more than a right angle to the current vector,\n draw a cap at the current point.\n */\n\n const nextVector = points[i + 1].vector\n\n const dpr = vec.dpr(vector, nextVector)\n\n if (dpr < 0) {\n const offset = vec.mul(vec.per(prevVector), radius)\n\n for (let t = 0; t < 1; t += 0.2) {\n tr = vec.rotAround(vec.add(point, offset), point, PI * -t)\n tl = vec.rotAround(vec.sub(point, offset), point, PI * t)\n\n rightPts.push(tr)\n leftPts.push(tl)\n }\n\n pl = tl\n pr = tr\n\n continue\n }\n /* \n Add regular points\n\n Project points to either side of the current point, using the\n calculated size as a distance. If a point's distance to the \n previous point on that side greater than the minimum distance\n (or if the corner is kinda sharp), add the points to the side's\n points array.\n */\n\n const offset = vec.mul(vec.per(vec.lrp(nextVector, vector, dpr)), radius)\n\n tl = vec.sub(point, offset)\n tr = vec.add(point, offset)\n\n const alwaysAdd = i === 1 || dpr < 0.25\n const minDistance = Math.pow(\n (runningLength > size ? size : size / 2) * smoothing,\n 2\n )\n\n if (alwaysAdd || vec.dist2(pl, tl) > minDistance) {\n leftPts.push(vec.lrp(pl, tl, streamline))\n pl = tl\n }\n\n if (alwaysAdd || vec.dist2(pr, tr) > minDistance) {\n rightPts.push(vec.lrp(pr, tr, streamline))\n pr = tr\n }\n\n // Set variables for next iteration\n\n prevPressure = pressure\n prevVector = vector\n }\n\n /*\n Drawing caps\n \n Now that we have our points on either side of the line, we need to\n draw caps at the start and end. Tapered lines don't have caps, but\n may have dots for very short lines.\n */\n\n const firstPoint = points[0]\n const lastPoint = points[len - 1]\n const isVeryShort = rightPts.length < 2 || leftPts.length < 2\n\n /* \n Draw a dot for very short or completed strokes\n \n If the line is too short to gather left or right points and if the line is\n not tapered on either side, draw a dot. If the line is tapered, then only\n draw a dot if the line is both very short and complete. If we draw a dot,\n we can just return those points.\n */\n\n if (isVeryShort && (!(taperStart || taperEnd) || isComplete)) {\n let ir = 0\n\n for (let i = 0; i < len; i++) {\n const { pressure, runningLength } = points[i]\n if (runningLength > size) {\n ir = getStrokeRadius(size, thinning, easing, pressure)\n break\n }\n }\n\n const start = vec.sub(\n firstPoint.point,\n vec.mul(\n vec.per(vec.uni(vec.vec(lastPoint.point, firstPoint.point))),\n ir || radius\n )\n )\n\n const dotPts: number[][] = []\n\n for (let t = 0, step = 0.1; t <= 1; t += step) {\n dotPts.push(vec.rotAround(start, firstPoint.point, PI * 2 * t))\n }\n\n return dotPts\n }\n\n /*\n Draw a start cap\n\n Unless the line has a tapered start, or unless the line has a tapered end\n and the line is very short, draw a start cap around the first point. Use\n the distance between the second left and right point for the cap's radius.\n Finally remove the first left and right points. :psyduck:\n */\n\n const startCap: number[][] = []\n\n if (!taperStart && !(taperEnd && isVeryShort)) {\n tr = rightPts[1]\n\n for (let i = 1; i < leftPts.length; i++) {\n if (!vec.isEqual(tr, leftPts[i])) {\n tl = leftPts[i]\n break\n }\n }\n\n if (!vec.isEqual(tr, tl)) {\n const start = vec.sub(\n firstPoint.point,\n vec.mul(vec.uni(vec.vec(tr, tl)), vec.dist(tr, tl) / 2)\n )\n\n for (let t = 0, step = 0.2; t <= 1; t += step) {\n startCap.push(vec.rotAround(start, firstPoint.point, PI * t))\n }\n\n leftPts.shift()\n rightPts.shift()\n }\n }\n\n /*\n Draw an end cap\n\n If the line does not have a tapered end, and unless the line has a tapered\n start and the line is very short, draw a cap around the last point. Finally, \n remove the last left and right points. Otherwise, add the last point. Note\n that This cap is a full-turn-and-a-half: this prevents incorrect caps on \n sharp end turns.\n */\n\n const endCap: number[][] = []\n\n if (!taperEnd && !(taperStart && isVeryShort)) {\n const start = vec.sub(\n lastPoint.point,\n vec.mul(vec.per(lastPoint.vector), radius)\n )\n\n for (let t = 0, step = 0.1; t <= 1; t += step) {\n endCap.push(vec.rotAround(start, lastPoint.point, PI * 3 * t))\n }\n } else {\n endCap.push(lastPoint.point)\n }\n\n /*\n Return the points in the correct windind order: begin on the left side, then \n continue around the end cap, then come back along the right side, and finally \n complete the start cap.\n */\n\n return leftPts.concat(endCap, rightPts.reverse(), startCap)\n}\n\n/**\n * ## getStroke\n * @description Returns a stroke as an array of outline points.\n * @param points An array of points (as `[x, y, pressure]` or `{x, y, pressure}`). Pressure is optional.\n * @param options An (optional) object with options.\n * @param options.size\tThe base size (diameter) of the stroke.\n * @param options.thinning The effect of pressure on the stroke's size.\n * @param options.smoothing\tHow much to soften the stroke's edges.\n * @param options.easing\tAn easing function to apply to each point's pressure.\n * @param options.simulatePressure Whether to simulate pressure based on velocity.\n * @param options.start Tapering and easing function for the start of the line.\n * @param options.end Tapering and easing function for the end of the line.\n * @param options.last Whether to handle the points as a completed stroke.\n */\nexport default function getStroke<\n T extends number[],\n K extends { x: number; y: number; pressure?: number }\n>(points: (T | K)[], options: StrokeOptions = {} as StrokeOptions): number[][] {\n return getStrokeOutlinePoints(getStrokePoints(points, options), options)\n}\n\nexport { StrokeOptions }\n"],"names":["add","A","B","sub","vec","mul","n","div","per","dpr","len","Math","hypot","len2","dist2","uni","dist","rotAround","C","r","s","sin","c","cos","px","py","nx","ny","lrp","t","isEqual","a","b","lerp","y1","y2","mu","clamp","max","min","toPointsArray","points","Array","isArray","map","x","y","pressure","getStrokeRadius","size","thinning","easing","PI","getStrokePoints","options","simulatePressure","streamline","pts","length","push","strokePoints","point","vector","distance","runningLength","i","j","curr","prev","totalLength","getStrokeOutlinePoints","smoothing","start","end","last","isComplete","taper","taperStart","taperStartEase","taperEnd","taperEndEase","leftPts","rightPts","prevPressure","slice","reduce","acc","cur","radius","prevVector","pl","pr","tl","tr","rp","sp","ts","te","nextVector","offset","alwaysAdd","minDistance","pow","firstPoint","lastPoint","isVeryShort","ir","dotPts","step","startCap","shift","endCap","concat","reverse","getStroke"],"mappings":"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"} |
| "use strict";function r(r,n){return[r[0]+n[0],r[1]+n[1]]}function n(r,n){return[r[0]-n[0],r[1]-n[1]]}function t(r,n){return[n[0]-r[0],n[1]-r[1]]}function e(r,n){return[r[0]*n,r[1]*n]}function i(r){return[r[1],-r[0]]}function o(r,n){return r[0]*n[0]+r[1]*n[1]}function u(r,t){return function(r){return r[0]*r[0]+r[1]*r[1]}(n(r,t))}function a(r){return function(r,n){return[r[0]/n,r[1]/n]}(r,function(r){return Math.hypot(r[0],r[1])}(r))}function v(r,n){return Math.hypot(r[1]-n[1],r[0]-n[0])}function s(r,n,t){var e=Math.sin(t),i=Math.cos(t),o=r[0]-n[0],u=r[1]-n[1];return[o*i-u*e+n[0],o*e+u*i+n[1]]}function f(n,i,o){return r(n,e(t(n,i),o))}function c(r,n){return r[0]===n[0]&&r[1]===n[1]}function p(r,n,t){return r*(1-t)+n*t}function h(r,n,t){return Math.max(n,Math.min(t,r))}function d(r,n,t,e){return void 0===e&&(e=.5),n?(e=h(t(e),0,1),(n<0?p(r,r+r*h(n,-.95,-.05),e):p(r-r*h(n,.05,.95),r,e))/2):r/2}Object.defineProperty(exports,"__esModule",{value:!0});var g=Math.min,l=Math.PI;function m(n,e){void 0===e&&(e={});var i=e.simulatePressure,u=e.streamline,s=void 0===u?.5:u,p=e.size,h=void 0===p?8:p;s/=2,void 0===i||i||(s/=2);var d=function(r){return Array.isArray(r[0])?r.map((function(r){var n=r[2];return[r[0],r[1],void 0===n?.5:n]})):r.map((function(r){var n=r.pressure;return[r.x,r.y,void 0===n?.5:n]}))}(n),g=d.length;if(0===g)return[];1===g&&d.push(r(d[0],[1,0]));for(var l=[{point:[d[0][0],d[0][1]],pressure:d[0][2],vector:[0,0],distance:0,runningLength:0}],m=1,M=0,L=d[m],x=l[M];m<g;L=d[++m],x=l[M]){var y=f(x.point,L,1-s);if(!c(x.point,y)){var P=L[2],k=a(t(y,x.point)),b=v(y,x.point);l.push({point:y,pressure:P,vector:k,distance:b,runningLength:x.runningLength+b}),M+=1}}for(var z=l[(g=l.length)-1].runningLength,A=g-2;A>1;A--){var O=l[A],S=O.runningLength,_=O.vector,j=o(l[A-1].vector,l[A].vector);if(z-S>h/2||j<.8){for(var w=A;w<g;w++)l[w].vector=_;break}}return l}function M(p,h){void 0===h&&(h={});var m=h.size,M=void 0===m?8:m,L=h.thinning,x=void 0===L?.5:L,y=h.smoothing,P=void 0===y?.5:y,k=h.simulatePressure,b=void 0===k||k,z=h.easing,A=void 0===z?function(r){return r}:z,O=h.start,S=void 0===O?{}:O,_=h.end,j=void 0===_?{}:_,w=h.last,I=void 0!==w&&w,q=h.streamline,B=void 0===q?.5:q;B/=2;var C=S.taper,D=void 0===C?0:C,E=S.easing,F=void 0===E?function(r){return r*(2-r)}:E,G=j.taper,H=void 0===G?0:G,J=j.easing,K=void 0===J?function(r){return--r*r*r+1}:J,N=p.length;if(0===N)return[];for(var Q=p[N-1].runningLength,R=[],T=[],U=p.slice(0,5).reduce((function(r,n){return(r+n.pressure)/2}),p[0].pressure),V=d(M,x,A,p[N-1].pressure),W=p[0].vector,X=p[0].point,Y=X,Z=X,$=Y,rr=1;rr<N-1;rr++){var nr=p[rr],tr=nr.point,er=nr.pressure,ir=nr.vector,or=nr.distance,ur=nr.runningLength;if(x){if(b){var ar=g(1,1-or/M),vr=g(1,or/M);er=g(1,U+vr/2*(ar-U))}V=d(M,x,A,er)}else V=M/2;var sr=ur<D?F(ur/D):1,fr=Q-ur<H?K((Q-ur)/H):1;V*=Math.min(sr,fr);var cr=p[rr+1].vector,pr=o(ir,cr);if(pr<0){for(var hr=e(i(W),V),dr=0;dr<1;dr+=.2)$=s(r(tr,hr),tr,l*-dr),Z=s(n(tr,hr),tr,l*dr),T.push($),R.push(Z);X=Z,Y=$}else{var gr=e(i(f(cr,ir,pr)),V);Z=n(tr,gr),$=r(tr,gr);var lr=1===rr||pr<.25,mr=Math.pow((ur>M?M:M/2)*P,2);(lr||u(X,Z)>mr)&&(R.push(f(X,Z,B)),X=Z),(lr||u(Y,$)>mr)&&(T.push(f(Y,$,B)),Y=$),U=er,W=ir}}var Mr=p[0],Lr=p[N-1],xr=T.length<2||R.length<2;if(xr&&(!D&&!H||I)){for(var yr=0,Pr=0;Pr<N;Pr++){var kr=p[Pr];if(kr.runningLength>M){yr=d(M,x,A,kr.pressure);break}}for(var br=n(Mr.point,e(i(a(t(Lr.point,Mr.point))),yr||V)),zr=[],Ar=0;Ar<=1;Ar+=.1)zr.push(s(br,Mr.point,2*l*Ar));return zr}var Or=[];if(!(D||H&&xr)){$=T[1];for(var Sr=1;Sr<R.length;Sr++)if(!c($,R[Sr])){Z=R[Sr];break}if(!c($,Z)){for(var _r=n(Mr.point,e(a(t($,Z)),v($,Z)/2)),jr=0;jr<=1;jr+=.2)Or.push(s(_r,Mr.point,l*jr));R.shift(),T.shift()}}var wr=[];if(H||D&&xr)wr.push(Lr.point);else for(var Ir=n(Lr.point,e(i(Lr.vector),V)),qr=0;qr<=1;qr+=.1)wr.push(s(Ir,Lr.point,3*l*qr));return R.concat(wr,T.reverse(),Or)}exports.default=function(r,n){return void 0===n&&(n={}),M(m(r,n),n)},exports.getStrokeOutlinePoints=M,exports.getStrokePoints=m; | ||
| //# sourceMappingURL=perfect-freehand.cjs.production.min.js.map |
| {"version":3,"file":"perfect-freehand.cjs.production.min.js","sources":["../src/vec.ts","../src/utils.ts","../src/index.ts"],"sourcesContent":["/**\n * Negate a vector.\n * @param A\n */\nexport function neg(A: number[]) {\n return [-A[0], -A[1]]\n}\n\n/**\n * Add vectors.\n * @param A\n * @param B\n */\nexport function add(A: number[], B: number[]) {\n return [A[0] + B[0], A[1] + B[1]]\n}\n\n/**\n * Subtract vectors.\n * @param A\n * @param B\n */\nexport function sub(A: number[], B: number[]) {\n return [A[0] - B[0], A[1] - B[1]]\n}\n\n/**\n * Get the vector from vectors A to B.\n * @param A\n * @param B\n */\nexport function vec(A: number[], B: number[]) {\n // A, B as vectors get the vector from A to B\n return [B[0] - A[0], B[1] - A[1]]\n}\n\n/**\n * Vector multiplication by scalar\n * @param A\n * @param n\n */\nexport function mul(A: number[], n: number) {\n return [A[0] * n, A[1] * n]\n}\n\n/**\n * Vector division by scalar.\n * @param A\n * @param n\n */\nexport function div(A: number[], n: number) {\n return [A[0] / n, A[1] / n]\n}\n\n/**\n * Perpendicular rotation of a vector A\n * @param A\n */\nexport function per(A: number[]) {\n return [A[1], -A[0]]\n}\n\n/**\n * Dot product\n * @param A\n * @param B\n */\nexport function dpr(A: number[], B: number[]) {\n return A[0] * B[0] + A[1] * B[1]\n}\n\n/**\n * Length of the vector\n * @param A\n */\nexport function len(A: number[]) {\n return Math.hypot(A[0], A[1])\n}\n\n/**\n * Length of the vector squared\n * @param A\n */\nexport function len2(A: number[]) {\n return A[0] * A[0] + A[1] * A[1]\n}\n\n/**\n * Dist length from A to B squared.\n * @param A\n * @param B\n */\nexport function dist2(A: number[], B: number[]) {\n return len2(sub(A, B))\n}\n\n/**\n * Get normalized / unit vector.\n * @param A\n */\nexport function uni(A: number[]) {\n return div(A, len(A))\n}\n\n/**\n * Dist length from A to B\n * @param A\n * @param B\n */\nexport function dist(A: number[], B: number[]) {\n return Math.hypot(A[1] - B[1], A[0] - B[0])\n}\n\n/**\n * Mean between two vectors or mid vector between two vectors\n * @param A\n * @param B\n */\nexport function med(A: number[], B: number[]) {\n return mul(add(A, B), 0.5)\n}\n\n/**\n * Rotate a vector around another vector by r (radians)\n * @param A vector\n * @param C center\n * @param r rotation in radians\n */\nexport function rotAround(A: number[], C: number[], r: number) {\n const s = Math.sin(r)\n const c = Math.cos(r)\n\n const px = A[0] - C[0]\n const py = A[1] - C[1]\n\n const nx = px * c - py * s\n const ny = px * s + py * c\n\n return [nx + C[0], ny + C[1]]\n}\n\n/**\n * Interpolate vector A to B with a scalar t\n * @param A\n * @param B\n * @param t scalar\n */\nexport function lrp(A: number[], B: number[], t: number) {\n return add(A, mul(vec(A, B), t))\n}\n\n// isLeft: >0 for counterclockwise\n// =0 for none (degenerate)\n// <0 for clockwise\nexport function isLeft(p1: number[], pc: number[], p2: number[]) {\n return (pc[0] - p1[0]) * (p2[1] - p1[1]) - (p2[0] - p1[0]) * (pc[1] - p1[1])\n}\n\nexport function clockwise(p1: number[], pc: number[], p2: number[]) {\n return isLeft(p1, pc, p2) > 0\n}\n\nexport function isEqual(a: number[], b: number[]) {\n return a[0] === b[0] && a[1] === b[1]\n}\n","import { isEqual } from './vec'\n\nexport function lerp(y1: number, y2: number, mu: number) {\n return y1 * (1 - mu) + y2 * mu\n}\n\nexport function clamp(n: number, a: number, b: number) {\n return Math.max(a, Math.min(b, n))\n}\n\n/**\n * Convert an array of points to the correct format ([x, y, radius])\n * @param points\n * @returns\n */\nexport function toPointsArray<\n T extends number[],\n K extends { x: number; y: number; pressure?: number }\n>(points: (T | K)[]): number[][] {\n if (Array.isArray(points[0])) {\n return (points as number[][]).map(([x, y, pressure = 0.5]) => [\n x,\n y,\n pressure,\n ])\n } else {\n return (points as {\n x: number\n y: number\n pressure?: number\n }[]).map(({ x, y, pressure = 0.5 }) => [x, y, pressure])\n }\n}\n\n/**\n * Compute a radius based on the pressure.\n * @param size\n * @param thinning\n * @param easing\n * @param pressure\n * @returns\n */\nexport function getStrokeRadius(\n size: number,\n thinning: number,\n easing: (t: number) => number,\n pressure = 0.5\n) {\n if (!thinning) return size / 2\n pressure = clamp(easing(pressure), 0, 1)\n return (\n (thinning < 0\n ? lerp(size, size + size * clamp(thinning, -0.95, -0.05), pressure)\n : lerp(size - size * clamp(thinning, 0.05, 0.95), size, pressure)) / 2\n )\n}\n\nexport function withoutDuplicates(pts: number[][]) {\n const unique: number[][] = []\n\n let prev: number[] | undefined = undefined\n\n for (let pt of pts) {\n if (prev && isEqual(prev, pt)) continue\n unique.push(pt)\n prev = pt\n }\n\n return pts\n}\n","import { toPointsArray, getStrokeRadius } from './utils'\nimport { StrokeOptions, StrokePoint } from './types'\nimport * as vec from './vec'\n\nconst { min, PI } = Math\n\n/**\n * ## getStrokePoints\n * @description Get points for a stroke.\n * @param points An array of points (as `[x, y, pressure]` or `{x, y, pressure}`). Pressure is optional.\n * @param streamline How much to streamline the stroke.\n * @param size The stroke's size.\n */\nexport function getStrokePoints<\n T extends number[],\n K extends { x: number; y: number; pressure?: number }\n>(points: (T | K)[], options = {} as StrokeOptions): StrokePoint[] {\n let { simulatePressure = true, streamline = 0.5, size = 8 } = options\n\n streamline /= 2\n\n if (!simulatePressure) {\n streamline /= 2\n }\n\n const pts = toPointsArray(points)\n\n let len = pts.length\n\n if (len === 0) return []\n\n if (len === 1) pts.push(vec.add(pts[0], [1, 0]))\n\n const strokePoints: StrokePoint[] = [\n {\n point: [pts[0][0], pts[0][1]],\n pressure: pts[0][2],\n vector: [0, 0],\n distance: 0,\n runningLength: 0,\n },\n ]\n\n for (\n let i = 1, j = 0, curr = pts[i], prev = strokePoints[j];\n i < len;\n i++, curr = pts[i], prev = strokePoints[j]\n ) {\n const point = vec.lrp(prev.point, curr, 1 - streamline)\n\n if (vec.isEqual(prev.point, point)) continue\n\n const pressure = curr[2]\n const vector = vec.uni(vec.vec(point, prev.point))\n const distance = vec.dist(point, prev.point)\n const runningLength = prev.runningLength + distance\n\n strokePoints.push({\n point,\n pressure,\n vector,\n distance,\n runningLength,\n })\n\n j += 1 // only increment j if we add an item to strokePoints\n }\n\n /* \n Align vectors at the end of the line\n\n Starting from the last point, work back until we've traveled more than\n half of the line's size (width). Take the current point's vector and then\n work forward, setting all remaining points' vectors to this vector. This\n removes the \"noise\" at the end of the line and allows for a better-facing\n end cap.\n */\n\n // Update the length to the length of the strokePoints array.\n len = strokePoints.length\n\n const totalLength = strokePoints[len - 1].runningLength\n\n for (let i = len - 2; i > 1; i--) {\n const { runningLength, vector } = strokePoints[i]\n const dpr = vec.dpr(strokePoints[i - 1].vector, strokePoints[i].vector)\n if (totalLength - runningLength > size / 2 || dpr < 0.8) {\n for (let j = i; j < len; j++) {\n strokePoints[j].vector = vector\n }\n break\n }\n }\n\n return strokePoints\n}\n\n/**\n * ## getStrokeOutlinePoints\n * @description Get an array of points (as `[x, y]`) representing the outline of a stroke.\n * @param points An array of points (as `[x, y, pressure]` or `{x, y, pressure}`). Pressure is optional.\n * @param options An (optional) object with options.\n * @param options.size\tThe base size (diameter) of the stroke.\n * @param options.thinning The effect of pressure on the stroke's size.\n * @param options.smoothing\tHow much to soften the stroke's edges.\n * @param options.easing\tAn easing function to apply to each point's pressure.\n * @param options.simulatePressure Whether to simulate pressure based on velocity.\n * @param options.start Tapering and easing function for the start of the line.\n * @param options.end Tapering and easing function for the end of the line.\n * @param options.last Whether to handle the points as a completed stroke.\n */\nexport function getStrokeOutlinePoints(\n points: StrokePoint[],\n options: Partial<StrokeOptions> = {} as Partial<StrokeOptions>\n): number[][] {\n const {\n size = 8,\n thinning = 0.5,\n smoothing = 0.5,\n simulatePressure = true,\n easing = t => t,\n start = {},\n end = {},\n last: isComplete = false,\n } = options\n\n let { streamline = 0.5 } = options\n\n streamline /= 2\n\n const {\n taper: taperStart = 0,\n easing: taperStartEase = t => t * (2 - t),\n } = start\n\n const {\n taper: taperEnd = 0,\n easing: taperEndEase = t => --t * t * t + 1,\n } = end\n\n // The number of points in the array\n const len = points.length\n\n // We can't do anything with an empty array.\n if (len === 0) return []\n\n // The total length of the line\n const totalLength = points[len - 1].runningLength\n\n // Our collected left and right points\n const leftPts: number[][] = []\n const rightPts: number[][] = []\n\n // Previous pressure (start with average of first five pressures)\n let prevPressure = points\n .slice(0, 5)\n .reduce((acc, cur) => (acc + cur.pressure) / 2, points[0].pressure)\n\n // The current radius\n let radius = getStrokeRadius(size, thinning, easing, points[len - 1].pressure)\n\n // Previous vector\n let prevVector = points[0].vector\n\n // Previous left and right points\n let pl = points[0].point\n let pr = pl\n\n // Temporary left and right points\n let tl = pl\n let tr = pr\n\n /*\n Find the outline's left and right points\n\n Iterating through the points and populate the rightPts and leftPts arrays,\n skipping the first and last pointsm, which will get caps later on.\n */\n\n for (let i = 1; i < len - 1; i++) {\n let { point, pressure, vector, distance, runningLength } = points[i]\n\n /*\n Calculate the radius\n\n If not thinning, the current point's radius will be half the size; or\n otherwise, the size will be based on the current (real or simulated)\n pressure. \n */\n\n if (thinning) {\n if (simulatePressure) {\n const rp = min(1, 1 - distance / size)\n const sp = min(1, distance / size)\n pressure = min(1, prevPressure + (rp - prevPressure) * (sp / 2))\n }\n\n radius = getStrokeRadius(size, thinning, easing, pressure)\n } else {\n radius = size / 2\n }\n\n /*\n Apply tapering\n\n If the current length is within the taper distance at either the\n start or the end, calculate the taper strengths. Apply the smaller \n of the two taper strengths to the radius.\n */\n\n const ts =\n runningLength < taperStart\n ? taperStartEase(runningLength / taperStart)\n : 1\n\n const te =\n totalLength - runningLength < taperEnd\n ? taperEndEase((totalLength - runningLength) / taperEnd)\n : 1\n\n radius *= Math.min(ts, te)\n\n /*\n Handle sharp corners\n\n Find the difference (dot product) between the current and next vector.\n If the next vector is at more than a right angle to the current vector,\n draw a cap at the current point.\n */\n\n const nextVector = points[i + 1].vector\n\n const dpr = vec.dpr(vector, nextVector)\n\n if (dpr < 0) {\n const offset = vec.mul(vec.per(prevVector), radius)\n\n for (let t = 0; t < 1; t += 0.2) {\n tr = vec.rotAround(vec.add(point, offset), point, PI * -t)\n tl = vec.rotAround(vec.sub(point, offset), point, PI * t)\n\n rightPts.push(tr)\n leftPts.push(tl)\n }\n\n pl = tl\n pr = tr\n\n continue\n }\n /* \n Add regular points\n\n Project points to either side of the current point, using the\n calculated size as a distance. If a point's distance to the \n previous point on that side greater than the minimum distance\n (or if the corner is kinda sharp), add the points to the side's\n points array.\n */\n\n const offset = vec.mul(vec.per(vec.lrp(nextVector, vector, dpr)), radius)\n\n tl = vec.sub(point, offset)\n tr = vec.add(point, offset)\n\n const alwaysAdd = i === 1 || dpr < 0.25\n const minDistance = Math.pow(\n (runningLength > size ? size : size / 2) * smoothing,\n 2\n )\n\n if (alwaysAdd || vec.dist2(pl, tl) > minDistance) {\n leftPts.push(vec.lrp(pl, tl, streamline))\n pl = tl\n }\n\n if (alwaysAdd || vec.dist2(pr, tr) > minDistance) {\n rightPts.push(vec.lrp(pr, tr, streamline))\n pr = tr\n }\n\n // Set variables for next iteration\n\n prevPressure = pressure\n prevVector = vector\n }\n\n /*\n Drawing caps\n \n Now that we have our points on either side of the line, we need to\n draw caps at the start and end. Tapered lines don't have caps, but\n may have dots for very short lines.\n */\n\n const firstPoint = points[0]\n const lastPoint = points[len - 1]\n const isVeryShort = rightPts.length < 2 || leftPts.length < 2\n\n /* \n Draw a dot for very short or completed strokes\n \n If the line is too short to gather left or right points and if the line is\n not tapered on either side, draw a dot. If the line is tapered, then only\n draw a dot if the line is both very short and complete. If we draw a dot,\n we can just return those points.\n */\n\n if (isVeryShort && (!(taperStart || taperEnd) || isComplete)) {\n let ir = 0\n\n for (let i = 0; i < len; i++) {\n const { pressure, runningLength } = points[i]\n if (runningLength > size) {\n ir = getStrokeRadius(size, thinning, easing, pressure)\n break\n }\n }\n\n const start = vec.sub(\n firstPoint.point,\n vec.mul(\n vec.per(vec.uni(vec.vec(lastPoint.point, firstPoint.point))),\n ir || radius\n )\n )\n\n const dotPts: number[][] = []\n\n for (let t = 0, step = 0.1; t <= 1; t += step) {\n dotPts.push(vec.rotAround(start, firstPoint.point, PI * 2 * t))\n }\n\n return dotPts\n }\n\n /*\n Draw a start cap\n\n Unless the line has a tapered start, or unless the line has a tapered end\n and the line is very short, draw a start cap around the first point. Use\n the distance between the second left and right point for the cap's radius.\n Finally remove the first left and right points. :psyduck:\n */\n\n const startCap: number[][] = []\n\n if (!taperStart && !(taperEnd && isVeryShort)) {\n tr = rightPts[1]\n\n for (let i = 1; i < leftPts.length; i++) {\n if (!vec.isEqual(tr, leftPts[i])) {\n tl = leftPts[i]\n break\n }\n }\n\n if (!vec.isEqual(tr, tl)) {\n const start = vec.sub(\n firstPoint.point,\n vec.mul(vec.uni(vec.vec(tr, tl)), vec.dist(tr, tl) / 2)\n )\n\n for (let t = 0, step = 0.2; t <= 1; t += step) {\n startCap.push(vec.rotAround(start, firstPoint.point, PI * t))\n }\n\n leftPts.shift()\n rightPts.shift()\n }\n }\n\n /*\n Draw an end cap\n\n If the line does not have a tapered end, and unless the line has a tapered\n start and the line is very short, draw a cap around the last point. Finally, \n remove the last left and right points. Otherwise, add the last point. Note\n that This cap is a full-turn-and-a-half: this prevents incorrect caps on \n sharp end turns.\n */\n\n const endCap: number[][] = []\n\n if (!taperEnd && !(taperStart && isVeryShort)) {\n const start = vec.sub(\n lastPoint.point,\n vec.mul(vec.per(lastPoint.vector), radius)\n )\n\n for (let t = 0, step = 0.1; t <= 1; t += step) {\n endCap.push(vec.rotAround(start, lastPoint.point, PI * 3 * t))\n }\n } else {\n endCap.push(lastPoint.point)\n }\n\n /*\n Return the points in the correct windind order: begin on the left side, then \n continue around the end cap, then come back along the right side, and finally \n complete the start cap.\n */\n\n return leftPts.concat(endCap, rightPts.reverse(), startCap)\n}\n\n/**\n * ## getStroke\n * @description Returns a stroke as an array of outline points.\n * @param points An array of points (as `[x, y, pressure]` or `{x, y, pressure}`). Pressure is optional.\n * @param options An (optional) object with options.\n * @param options.size\tThe base size (diameter) of the stroke.\n * @param options.thinning The effect of pressure on the stroke's size.\n * @param options.smoothing\tHow much to soften the stroke's edges.\n * @param options.easing\tAn easing function to apply to each point's pressure.\n * @param options.simulatePressure Whether to simulate pressure based on velocity.\n * @param options.start Tapering and easing function for the start of the line.\n * @param options.end Tapering and easing function for the end of the line.\n * @param options.last Whether to handle the points as a completed stroke.\n */\nexport default function getStroke<\n T extends number[],\n K extends { x: number; y: number; pressure?: number }\n>(points: (T | K)[], options: StrokeOptions = {} as StrokeOptions): number[][] {\n return getStrokeOutlinePoints(getStrokePoints(points, options), options)\n}\n\nexport { StrokeOptions }\n"],"names":["add","A","B","sub","vec","mul","n","per","dpr","dist2","len2","uni","div","Math","hypot","len","dist","rotAround","C","r","s","sin","c","cos","px","py","lrp","t","isEqual","a","b","lerp","y1","y2","mu","clamp","max","min","getStrokeRadius","size","thinning","easing","pressure","PI","getStrokePoints","points","options","simulatePressure","streamline","pts","Array","isArray","map","x","y","toPointsArray","length","push","strokePoints","point","vector","distance","runningLength","i","j","curr","prev","totalLength","getStrokeOutlinePoints","smoothing","start","end","last","isComplete","taper","taperStart","taperStartEase","taperEnd","taperEndEase","leftPts","rightPts","prevPressure","slice","reduce","acc","cur","radius","prevVector","pl","pr","tl","tr","rp","sp","ts","te","nextVector","offset","alwaysAdd","minDistance","pow","firstPoint","lastPoint","isVeryShort","ir","dotPts","startCap","shift","endCap","concat","reverse"],"mappings":"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"} |
| /** | ||
| * Negate a vector. | ||
| * @param A | ||
| */ | ||
| /** | ||
| * Add vectors. | ||
| * @param A | ||
| * @param B | ||
| */ | ||
| function add(A, B) { | ||
| return [A[0] + B[0], A[1] + B[1]]; | ||
| } | ||
| /** | ||
| * Subtract vectors. | ||
| * @param A | ||
| * @param B | ||
| */ | ||
| function sub(A, B) { | ||
| return [A[0] - B[0], A[1] - B[1]]; | ||
| } | ||
| /** | ||
| * Get the vector from vectors A to B. | ||
| * @param A | ||
| * @param B | ||
| */ | ||
| function vec(A, B) { | ||
| // A, B as vectors get the vector from A to B | ||
| return [B[0] - A[0], B[1] - A[1]]; | ||
| } | ||
| /** | ||
| * Vector multiplication by scalar | ||
| * @param A | ||
| * @param n | ||
| */ | ||
| function mul(A, n) { | ||
| return [A[0] * n, A[1] * n]; | ||
| } | ||
| /** | ||
| * Vector division by scalar. | ||
| * @param A | ||
| * @param n | ||
| */ | ||
| function div(A, n) { | ||
| return [A[0] / n, A[1] / n]; | ||
| } | ||
| /** | ||
| * Perpendicular rotation of a vector A | ||
| * @param A | ||
| */ | ||
| function per(A) { | ||
| return [A[1], -A[0]]; | ||
| } | ||
| /** | ||
| * Dot product | ||
| * @param A | ||
| * @param B | ||
| */ | ||
| function dpr(A, B) { | ||
| return A[0] * B[0] + A[1] * B[1]; | ||
| } | ||
| /** | ||
| * Length of the vector | ||
| * @param A | ||
| */ | ||
| function len(A) { | ||
| return Math.hypot(A[0], A[1]); | ||
| } | ||
| /** | ||
| * Length of the vector squared | ||
| * @param A | ||
| */ | ||
| function len2(A) { | ||
| return A[0] * A[0] + A[1] * A[1]; | ||
| } | ||
| /** | ||
| * Dist length from A to B squared. | ||
| * @param A | ||
| * @param B | ||
| */ | ||
| function dist2(A, B) { | ||
| return len2(sub(A, B)); | ||
| } | ||
| /** | ||
| * Get normalized / unit vector. | ||
| * @param A | ||
| */ | ||
| function uni(A) { | ||
| return div(A, len(A)); | ||
| } | ||
| /** | ||
| * Dist length from A to B | ||
| * @param A | ||
| * @param B | ||
| */ | ||
| function dist(A, B) { | ||
| return Math.hypot(A[1] - B[1], A[0] - B[0]); | ||
| } | ||
| /** | ||
| * Rotate a vector around another vector by r (radians) | ||
| * @param A vector | ||
| * @param C center | ||
| * @param r rotation in radians | ||
| */ | ||
| function rotAround(A, C, r) { | ||
| var s = Math.sin(r); | ||
| var c = Math.cos(r); | ||
| var px = A[0] - C[0]; | ||
| var py = A[1] - C[1]; | ||
| var nx = px * c - py * s; | ||
| var ny = px * s + py * c; | ||
| return [nx + C[0], ny + C[1]]; | ||
| } | ||
| /** | ||
| * Interpolate vector A to B with a scalar t | ||
| * @param A | ||
| * @param B | ||
| * @param t scalar | ||
| */ | ||
| function lrp(A, B, t) { | ||
| return add(A, mul(vec(A, B), t)); | ||
| } // isLeft: >0 for counterclockwise | ||
| function isEqual(a, b) { | ||
| return a[0] === b[0] && a[1] === b[1]; | ||
| } | ||
| function lerp(y1, y2, mu) { | ||
| return y1 * (1 - mu) + y2 * mu; | ||
| } | ||
| function clamp(n, a, b) { | ||
| return Math.max(a, Math.min(b, n)); | ||
| } | ||
| /** | ||
| * Convert an array of points to the correct format ([x, y, radius]) | ||
| * @param points | ||
| * @returns | ||
| */ | ||
| function toPointsArray(points) { | ||
| if (Array.isArray(points[0])) { | ||
| return points.map(function (_ref) { | ||
| var x = _ref[0], | ||
| y = _ref[1], | ||
| _ref$ = _ref[2], | ||
| pressure = _ref$ === void 0 ? 0.5 : _ref$; | ||
| return [x, y, pressure]; | ||
| }); | ||
| } else { | ||
| return points.map(function (_ref2) { | ||
| var x = _ref2.x, | ||
| y = _ref2.y, | ||
| _ref2$pressure = _ref2.pressure, | ||
| pressure = _ref2$pressure === void 0 ? 0.5 : _ref2$pressure; | ||
| return [x, y, pressure]; | ||
| }); | ||
| } | ||
| } | ||
| /** | ||
| * Compute a radius based on the pressure. | ||
| * @param size | ||
| * @param thinning | ||
| * @param easing | ||
| * @param pressure | ||
| * @returns | ||
| */ | ||
| function getStrokeRadius(size, thinning, easing, pressure) { | ||
| if (pressure === void 0) { | ||
| pressure = 0.5; | ||
| } | ||
| if (!thinning) return size / 2; | ||
| pressure = clamp(easing(pressure), 0, 1); | ||
| return (thinning < 0 ? lerp(size, size + size * clamp(thinning, -0.95, -0.05), pressure) : lerp(size - size * clamp(thinning, 0.05, 0.95), size, pressure)) / 2; | ||
| } | ||
| var min = Math.min, | ||
| PI = Math.PI; | ||
| /** | ||
| * ## getStrokePoints | ||
| * @description Get points for a stroke. | ||
| * @param points An array of points (as `[x, y, pressure]` or `{x, y, pressure}`). Pressure is optional. | ||
| * @param streamline How much to streamline the stroke. | ||
| * @param size The stroke's size. | ||
| */ | ||
| function getStrokePoints(points, options) { | ||
| if (options === void 0) { | ||
| options = {}; | ||
| } | ||
| var _options = options, | ||
| _options$simulatePres = _options.simulatePressure, | ||
| simulatePressure = _options$simulatePres === void 0 ? true : _options$simulatePres, | ||
| _options$streamline = _options.streamline, | ||
| streamline = _options$streamline === void 0 ? 0.5 : _options$streamline, | ||
| _options$size = _options.size, | ||
| size = _options$size === void 0 ? 8 : _options$size; | ||
| streamline /= 2; | ||
| if (!simulatePressure) { | ||
| streamline /= 2; | ||
| } | ||
| var pts = toPointsArray(points); | ||
| var len = pts.length; | ||
| if (len === 0) return []; | ||
| if (len === 1) pts.push(add(pts[0], [1, 0])); | ||
| var strokePoints = [{ | ||
| point: [pts[0][0], pts[0][1]], | ||
| pressure: pts[0][2], | ||
| vector: [0, 0], | ||
| distance: 0, | ||
| runningLength: 0 | ||
| }]; | ||
| for (var i = 1, j = 0, curr = pts[i], prev = strokePoints[j]; i < len; i++, curr = pts[i], prev = strokePoints[j]) { | ||
| var point = lrp(prev.point, curr, 1 - streamline); | ||
| if (isEqual(prev.point, point)) continue; | ||
| var pressure = curr[2]; | ||
| var vector = uni(vec(point, prev.point)); | ||
| var distance = dist(point, prev.point); | ||
| var runningLength = prev.runningLength + distance; | ||
| strokePoints.push({ | ||
| point: point, | ||
| pressure: pressure, | ||
| vector: vector, | ||
| distance: distance, | ||
| runningLength: runningLength | ||
| }); | ||
| j += 1; // only increment j if we add an item to strokePoints | ||
| } | ||
| /* | ||
| Align vectors at the end of the line | ||
| Starting from the last point, work back until we've traveled more than | ||
| half of the line's size (width). Take the current point's vector and then | ||
| work forward, setting all remaining points' vectors to this vector. This | ||
| removes the "noise" at the end of the line and allows for a better-facing | ||
| end cap. | ||
| */ | ||
| // Update the length to the length of the strokePoints array. | ||
| len = strokePoints.length; | ||
| var totalLength = strokePoints[len - 1].runningLength; | ||
| for (var _i = len - 2; _i > 1; _i--) { | ||
| var _strokePoints$_i = strokePoints[_i], | ||
| _runningLength = _strokePoints$_i.runningLength, | ||
| _vector = _strokePoints$_i.vector; | ||
| var dpr$1 = dpr(strokePoints[_i - 1].vector, strokePoints[_i].vector); | ||
| if (totalLength - _runningLength > size / 2 || dpr$1 < 0.8) { | ||
| for (var _j = _i; _j < len; _j++) { | ||
| strokePoints[_j].vector = _vector; | ||
| } | ||
| break; | ||
| } | ||
| } | ||
| return strokePoints; | ||
| } | ||
| /** | ||
| * ## getStrokeOutlinePoints | ||
| * @description Get an array of points (as `[x, y]`) representing the outline of a stroke. | ||
| * @param points An array of points (as `[x, y, pressure]` or `{x, y, pressure}`). Pressure is optional. | ||
| * @param options An (optional) object with options. | ||
| * @param options.size The base size (diameter) of the stroke. | ||
| * @param options.thinning The effect of pressure on the stroke's size. | ||
| * @param options.smoothing How much to soften the stroke's edges. | ||
| * @param options.easing An easing function to apply to each point's pressure. | ||
| * @param options.simulatePressure Whether to simulate pressure based on velocity. | ||
| * @param options.start Tapering and easing function for the start of the line. | ||
| * @param options.end Tapering and easing function for the end of the line. | ||
| * @param options.last Whether to handle the points as a completed stroke. | ||
| */ | ||
| function getStrokeOutlinePoints(points, options) { | ||
| if (options === void 0) { | ||
| options = {}; | ||
| } | ||
| var _options2 = options, | ||
| _options2$size = _options2.size, | ||
| size = _options2$size === void 0 ? 8 : _options2$size, | ||
| _options2$thinning = _options2.thinning, | ||
| thinning = _options2$thinning === void 0 ? 0.5 : _options2$thinning, | ||
| _options2$smoothing = _options2.smoothing, | ||
| smoothing = _options2$smoothing === void 0 ? 0.5 : _options2$smoothing, | ||
| _options2$simulatePre = _options2.simulatePressure, | ||
| simulatePressure = _options2$simulatePre === void 0 ? true : _options2$simulatePre, | ||
| _options2$easing = _options2.easing, | ||
| easing = _options2$easing === void 0 ? function (t) { | ||
| return t; | ||
| } : _options2$easing, | ||
| _options2$start = _options2.start, | ||
| start = _options2$start === void 0 ? {} : _options2$start, | ||
| _options2$end = _options2.end, | ||
| end = _options2$end === void 0 ? {} : _options2$end, | ||
| _options2$last = _options2.last, | ||
| isComplete = _options2$last === void 0 ? false : _options2$last; | ||
| var _options3 = options, | ||
| _options3$streamline = _options3.streamline, | ||
| streamline = _options3$streamline === void 0 ? 0.5 : _options3$streamline; | ||
| streamline /= 2; | ||
| var _start$taper = start.taper, | ||
| taperStart = _start$taper === void 0 ? 0 : _start$taper, | ||
| _start$easing = start.easing, | ||
| taperStartEase = _start$easing === void 0 ? function (t) { | ||
| return t * (2 - t); | ||
| } : _start$easing; | ||
| var _end$taper = end.taper, | ||
| taperEnd = _end$taper === void 0 ? 0 : _end$taper, | ||
| _end$easing = end.easing, | ||
| taperEndEase = _end$easing === void 0 ? function (t) { | ||
| return --t * t * t + 1; | ||
| } : _end$easing; // The number of points in the array | ||
| var len = points.length; // We can't do anything with an empty array. | ||
| if (len === 0) return []; // The total length of the line | ||
| var totalLength = points[len - 1].runningLength; // Our collected left and right points | ||
| var leftPts = []; | ||
| var rightPts = []; // Previous pressure (start with average of first five pressures) | ||
| var prevPressure = points.slice(0, 5).reduce(function (acc, cur) { | ||
| return (acc + cur.pressure) / 2; | ||
| }, points[0].pressure); // The current radius | ||
| var radius = getStrokeRadius(size, thinning, easing, points[len - 1].pressure); // Previous vector | ||
| var prevVector = points[0].vector; // Previous left and right points | ||
| var pl = points[0].point; | ||
| var pr = pl; // Temporary left and right points | ||
| var tl = pl; | ||
| var tr = pr; | ||
| /* | ||
| Find the outline's left and right points | ||
| Iterating through the points and populate the rightPts and leftPts arrays, | ||
| skipping the first and last pointsm, which will get caps later on. | ||
| */ | ||
| for (var i = 1; i < len - 1; i++) { | ||
| var _points$i = points[i], | ||
| point = _points$i.point, | ||
| pressure = _points$i.pressure, | ||
| vector = _points$i.vector, | ||
| distance = _points$i.distance, | ||
| runningLength = _points$i.runningLength; | ||
| /* | ||
| Calculate the radius | ||
| If not thinning, the current point's radius will be half the size; or | ||
| otherwise, the size will be based on the current (real or simulated) | ||
| pressure. | ||
| */ | ||
| if (thinning) { | ||
| if (simulatePressure) { | ||
| var rp = min(1, 1 - distance / size); | ||
| var sp = min(1, distance / size); | ||
| pressure = min(1, prevPressure + (rp - prevPressure) * (sp / 2)); | ||
| } | ||
| radius = getStrokeRadius(size, thinning, easing, pressure); | ||
| } else { | ||
| radius = size / 2; | ||
| } | ||
| /* | ||
| Apply tapering | ||
| If the current length is within the taper distance at either the | ||
| start or the end, calculate the taper strengths. Apply the smaller | ||
| of the two taper strengths to the radius. | ||
| */ | ||
| var ts = runningLength < taperStart ? taperStartEase(runningLength / taperStart) : 1; | ||
| var te = totalLength - runningLength < taperEnd ? taperEndEase((totalLength - runningLength) / taperEnd) : 1; | ||
| radius *= Math.min(ts, te); | ||
| /* | ||
| Handle sharp corners | ||
| Find the difference (dot product) between the current and next vector. | ||
| If the next vector is at more than a right angle to the current vector, | ||
| draw a cap at the current point. | ||
| */ | ||
| var nextVector = points[i + 1].vector; | ||
| var dpr$1 = dpr(vector, nextVector); | ||
| if (dpr$1 < 0) { | ||
| var _offset = mul(per(prevVector), radius); | ||
| for (var t = 0; t < 1; t += 0.2) { | ||
| tr = rotAround(add(point, _offset), point, PI * -t); | ||
| tl = rotAround(sub(point, _offset), point, PI * t); | ||
| rightPts.push(tr); | ||
| leftPts.push(tl); | ||
| } | ||
| pl = tl; | ||
| pr = tr; | ||
| continue; | ||
| } | ||
| /* | ||
| Add regular points | ||
| Project points to either side of the current point, using the | ||
| calculated size as a distance. If a point's distance to the | ||
| previous point on that side greater than the minimum distance | ||
| (or if the corner is kinda sharp), add the points to the side's | ||
| points array. | ||
| */ | ||
| var offset = mul(per(lrp(nextVector, vector, dpr$1)), radius); | ||
| tl = sub(point, offset); | ||
| tr = add(point, offset); | ||
| var alwaysAdd = i === 1 || dpr$1 < 0.25; | ||
| var minDistance = Math.pow((runningLength > size ? size : size / 2) * smoothing, 2); | ||
| if (alwaysAdd || dist2(pl, tl) > minDistance) { | ||
| leftPts.push(lrp(pl, tl, streamline)); | ||
| pl = tl; | ||
| } | ||
| if (alwaysAdd || dist2(pr, tr) > minDistance) { | ||
| rightPts.push(lrp(pr, tr, streamline)); | ||
| pr = tr; | ||
| } // Set variables for next iteration | ||
| prevPressure = pressure; | ||
| prevVector = vector; | ||
| } | ||
| /* | ||
| Drawing caps | ||
| Now that we have our points on either side of the line, we need to | ||
| draw caps at the start and end. Tapered lines don't have caps, but | ||
| may have dots for very short lines. | ||
| */ | ||
| var firstPoint = points[0]; | ||
| var lastPoint = points[len - 1]; | ||
| var isVeryShort = rightPts.length < 2 || leftPts.length < 2; | ||
| /* | ||
| Draw a dot for very short or completed strokes | ||
| If the line is too short to gather left or right points and if the line is | ||
| not tapered on either side, draw a dot. If the line is tapered, then only | ||
| draw a dot if the line is both very short and complete. If we draw a dot, | ||
| we can just return those points. | ||
| */ | ||
| if (isVeryShort && (!(taperStart || taperEnd) || isComplete)) { | ||
| var ir = 0; | ||
| for (var _i2 = 0; _i2 < len; _i2++) { | ||
| var _points$_i = points[_i2], | ||
| _pressure = _points$_i.pressure, | ||
| _runningLength2 = _points$_i.runningLength; | ||
| if (_runningLength2 > size) { | ||
| ir = getStrokeRadius(size, thinning, easing, _pressure); | ||
| break; | ||
| } | ||
| } | ||
| var _start = sub(firstPoint.point, mul(per(uni(vec(lastPoint.point, firstPoint.point))), ir || radius)); | ||
| var dotPts = []; | ||
| for (var _t = 0, step = 0.1; _t <= 1; _t += step) { | ||
| dotPts.push(rotAround(_start, firstPoint.point, PI * 2 * _t)); | ||
| } | ||
| return dotPts; | ||
| } | ||
| /* | ||
| Draw a start cap | ||
| Unless the line has a tapered start, or unless the line has a tapered end | ||
| and the line is very short, draw a start cap around the first point. Use | ||
| the distance between the second left and right point for the cap's radius. | ||
| Finally remove the first left and right points. :psyduck: | ||
| */ | ||
| var startCap = []; | ||
| if (!taperStart && !(taperEnd && isVeryShort)) { | ||
| tr = rightPts[1]; | ||
| for (var _i3 = 1; _i3 < leftPts.length; _i3++) { | ||
| if (!isEqual(tr, leftPts[_i3])) { | ||
| tl = leftPts[_i3]; | ||
| break; | ||
| } | ||
| } | ||
| if (!isEqual(tr, tl)) { | ||
| var _start2 = sub(firstPoint.point, mul(uni(vec(tr, tl)), dist(tr, tl) / 2)); | ||
| for (var _t2 = 0, _step = 0.2; _t2 <= 1; _t2 += _step) { | ||
| startCap.push(rotAround(_start2, firstPoint.point, PI * _t2)); | ||
| } | ||
| leftPts.shift(); | ||
| rightPts.shift(); | ||
| } | ||
| } | ||
| /* | ||
| Draw an end cap | ||
| If the line does not have a tapered end, and unless the line has a tapered | ||
| start and the line is very short, draw a cap around the last point. Finally, | ||
| remove the last left and right points. Otherwise, add the last point. Note | ||
| that This cap is a full-turn-and-a-half: this prevents incorrect caps on | ||
| sharp end turns. | ||
| */ | ||
| var endCap = []; | ||
| if (!taperEnd && !(taperStart && isVeryShort)) { | ||
| var _start3 = sub(lastPoint.point, mul(per(lastPoint.vector), radius)); | ||
| for (var _t3 = 0, _step2 = 0.1; _t3 <= 1; _t3 += _step2) { | ||
| endCap.push(rotAround(_start3, lastPoint.point, PI * 3 * _t3)); | ||
| } | ||
| } else { | ||
| endCap.push(lastPoint.point); | ||
| } | ||
| /* | ||
| Return the points in the correct windind order: begin on the left side, then | ||
| continue around the end cap, then come back along the right side, and finally | ||
| complete the start cap. | ||
| */ | ||
| return leftPts.concat(endCap, rightPts.reverse(), startCap); | ||
| } | ||
| /** | ||
| * ## getStroke | ||
| * @description Returns a stroke as an array of outline points. | ||
| * @param points An array of points (as `[x, y, pressure]` or `{x, y, pressure}`). Pressure is optional. | ||
| * @param options An (optional) object with options. | ||
| * @param options.size The base size (diameter) of the stroke. | ||
| * @param options.thinning The effect of pressure on the stroke's size. | ||
| * @param options.smoothing How much to soften the stroke's edges. | ||
| * @param options.easing An easing function to apply to each point's pressure. | ||
| * @param options.simulatePressure Whether to simulate pressure based on velocity. | ||
| * @param options.start Tapering and easing function for the start of the line. | ||
| * @param options.end Tapering and easing function for the end of the line. | ||
| * @param options.last Whether to handle the points as a completed stroke. | ||
| */ | ||
| function getStroke(points, options) { | ||
| if (options === void 0) { | ||
| options = {}; | ||
| } | ||
| return getStrokeOutlinePoints(getStrokePoints(points, options), options); | ||
| } | ||
| export default getStroke; | ||
| export { getStrokeOutlinePoints, getStrokePoints }; | ||
| //# sourceMappingURL=perfect-freehand.esm.js.map |
| {"version":3,"file":"perfect-freehand.esm.js","sources":["../src/vec.ts","../src/utils.ts","../src/index.ts"],"sourcesContent":["/**\n * Negate a vector.\n * @param A\n */\nexport function neg(A: number[]) {\n return [-A[0], -A[1]]\n}\n\n/**\n * Add vectors.\n * @param A\n * @param B\n */\nexport function add(A: number[], B: number[]) {\n return [A[0] + B[0], A[1] + B[1]]\n}\n\n/**\n * Subtract vectors.\n * @param A\n * @param B\n */\nexport function sub(A: number[], B: number[]) {\n return [A[0] - B[0], A[1] - B[1]]\n}\n\n/**\n * Get the vector from vectors A to B.\n * @param A\n * @param B\n */\nexport function vec(A: number[], B: number[]) {\n // A, B as vectors get the vector from A to B\n return [B[0] - A[0], B[1] - A[1]]\n}\n\n/**\n * Vector multiplication by scalar\n * @param A\n * @param n\n */\nexport function mul(A: number[], n: number) {\n return [A[0] * n, A[1] * n]\n}\n\n/**\n * Vector division by scalar.\n * @param A\n * @param n\n */\nexport function div(A: number[], n: number) {\n return [A[0] / n, A[1] / n]\n}\n\n/**\n * Perpendicular rotation of a vector A\n * @param A\n */\nexport function per(A: number[]) {\n return [A[1], -A[0]]\n}\n\n/**\n * Dot product\n * @param A\n * @param B\n */\nexport function dpr(A: number[], B: number[]) {\n return A[0] * B[0] + A[1] * B[1]\n}\n\n/**\n * Length of the vector\n * @param A\n */\nexport function len(A: number[]) {\n return Math.hypot(A[0], A[1])\n}\n\n/**\n * Length of the vector squared\n * @param A\n */\nexport function len2(A: number[]) {\n return A[0] * A[0] + A[1] * A[1]\n}\n\n/**\n * Dist length from A to B squared.\n * @param A\n * @param B\n */\nexport function dist2(A: number[], B: number[]) {\n return len2(sub(A, B))\n}\n\n/**\n * Get normalized / unit vector.\n * @param A\n */\nexport function uni(A: number[]) {\n return div(A, len(A))\n}\n\n/**\n * Dist length from A to B\n * @param A\n * @param B\n */\nexport function dist(A: number[], B: number[]) {\n return Math.hypot(A[1] - B[1], A[0] - B[0])\n}\n\n/**\n * Mean between two vectors or mid vector between two vectors\n * @param A\n * @param B\n */\nexport function med(A: number[], B: number[]) {\n return mul(add(A, B), 0.5)\n}\n\n/**\n * Rotate a vector around another vector by r (radians)\n * @param A vector\n * @param C center\n * @param r rotation in radians\n */\nexport function rotAround(A: number[], C: number[], r: number) {\n const s = Math.sin(r)\n const c = Math.cos(r)\n\n const px = A[0] - C[0]\n const py = A[1] - C[1]\n\n const nx = px * c - py * s\n const ny = px * s + py * c\n\n return [nx + C[0], ny + C[1]]\n}\n\n/**\n * Interpolate vector A to B with a scalar t\n * @param A\n * @param B\n * @param t scalar\n */\nexport function lrp(A: number[], B: number[], t: number) {\n return add(A, mul(vec(A, B), t))\n}\n\n// isLeft: >0 for counterclockwise\n// =0 for none (degenerate)\n// <0 for clockwise\nexport function isLeft(p1: number[], pc: number[], p2: number[]) {\n return (pc[0] - p1[0]) * (p2[1] - p1[1]) - (p2[0] - p1[0]) * (pc[1] - p1[1])\n}\n\nexport function clockwise(p1: number[], pc: number[], p2: number[]) {\n return isLeft(p1, pc, p2) > 0\n}\n\nexport function isEqual(a: number[], b: number[]) {\n return a[0] === b[0] && a[1] === b[1]\n}\n","import { isEqual } from './vec'\n\nexport function lerp(y1: number, y2: number, mu: number) {\n return y1 * (1 - mu) + y2 * mu\n}\n\nexport function clamp(n: number, a: number, b: number) {\n return Math.max(a, Math.min(b, n))\n}\n\n/**\n * Convert an array of points to the correct format ([x, y, radius])\n * @param points\n * @returns\n */\nexport function toPointsArray<\n T extends number[],\n K extends { x: number; y: number; pressure?: number }\n>(points: (T | K)[]): number[][] {\n if (Array.isArray(points[0])) {\n return (points as number[][]).map(([x, y, pressure = 0.5]) => [\n x,\n y,\n pressure,\n ])\n } else {\n return (points as {\n x: number\n y: number\n pressure?: number\n }[]).map(({ x, y, pressure = 0.5 }) => [x, y, pressure])\n }\n}\n\n/**\n * Compute a radius based on the pressure.\n * @param size\n * @param thinning\n * @param easing\n * @param pressure\n * @returns\n */\nexport function getStrokeRadius(\n size: number,\n thinning: number,\n easing: (t: number) => number,\n pressure = 0.5\n) {\n if (!thinning) return size / 2\n pressure = clamp(easing(pressure), 0, 1)\n return (\n (thinning < 0\n ? lerp(size, size + size * clamp(thinning, -0.95, -0.05), pressure)\n : lerp(size - size * clamp(thinning, 0.05, 0.95), size, pressure)) / 2\n )\n}\n\nexport function withoutDuplicates(pts: number[][]) {\n const unique: number[][] = []\n\n let prev: number[] | undefined = undefined\n\n for (let pt of pts) {\n if (prev && isEqual(prev, pt)) continue\n unique.push(pt)\n prev = pt\n }\n\n return pts\n}\n","import { toPointsArray, getStrokeRadius } from './utils'\nimport { StrokeOptions, StrokePoint } from './types'\nimport * as vec from './vec'\n\nconst { min, PI } = Math\n\n/**\n * ## getStrokePoints\n * @description Get points for a stroke.\n * @param points An array of points (as `[x, y, pressure]` or `{x, y, pressure}`). Pressure is optional.\n * @param streamline How much to streamline the stroke.\n * @param size The stroke's size.\n */\nexport function getStrokePoints<\n T extends number[],\n K extends { x: number; y: number; pressure?: number }\n>(points: (T | K)[], options = {} as StrokeOptions): StrokePoint[] {\n let { simulatePressure = true, streamline = 0.5, size = 8 } = options\n\n streamline /= 2\n\n if (!simulatePressure) {\n streamline /= 2\n }\n\n const pts = toPointsArray(points)\n\n let len = pts.length\n\n if (len === 0) return []\n\n if (len === 1) pts.push(vec.add(pts[0], [1, 0]))\n\n const strokePoints: StrokePoint[] = [\n {\n point: [pts[0][0], pts[0][1]],\n pressure: pts[0][2],\n vector: [0, 0],\n distance: 0,\n runningLength: 0,\n },\n ]\n\n for (\n let i = 1, j = 0, curr = pts[i], prev = strokePoints[j];\n i < len;\n i++, curr = pts[i], prev = strokePoints[j]\n ) {\n const point = vec.lrp(prev.point, curr, 1 - streamline)\n\n if (vec.isEqual(prev.point, point)) continue\n\n const pressure = curr[2]\n const vector = vec.uni(vec.vec(point, prev.point))\n const distance = vec.dist(point, prev.point)\n const runningLength = prev.runningLength + distance\n\n strokePoints.push({\n point,\n pressure,\n vector,\n distance,\n runningLength,\n })\n\n j += 1 // only increment j if we add an item to strokePoints\n }\n\n /* \n Align vectors at the end of the line\n\n Starting from the last point, work back until we've traveled more than\n half of the line's size (width). Take the current point's vector and then\n work forward, setting all remaining points' vectors to this vector. This\n removes the \"noise\" at the end of the line and allows for a better-facing\n end cap.\n */\n\n // Update the length to the length of the strokePoints array.\n len = strokePoints.length\n\n const totalLength = strokePoints[len - 1].runningLength\n\n for (let i = len - 2; i > 1; i--) {\n const { runningLength, vector } = strokePoints[i]\n const dpr = vec.dpr(strokePoints[i - 1].vector, strokePoints[i].vector)\n if (totalLength - runningLength > size / 2 || dpr < 0.8) {\n for (let j = i; j < len; j++) {\n strokePoints[j].vector = vector\n }\n break\n }\n }\n\n return strokePoints\n}\n\n/**\n * ## getStrokeOutlinePoints\n * @description Get an array of points (as `[x, y]`) representing the outline of a stroke.\n * @param points An array of points (as `[x, y, pressure]` or `{x, y, pressure}`). Pressure is optional.\n * @param options An (optional) object with options.\n * @param options.size\tThe base size (diameter) of the stroke.\n * @param options.thinning The effect of pressure on the stroke's size.\n * @param options.smoothing\tHow much to soften the stroke's edges.\n * @param options.easing\tAn easing function to apply to each point's pressure.\n * @param options.simulatePressure Whether to simulate pressure based on velocity.\n * @param options.start Tapering and easing function for the start of the line.\n * @param options.end Tapering and easing function for the end of the line.\n * @param options.last Whether to handle the points as a completed stroke.\n */\nexport function getStrokeOutlinePoints(\n points: StrokePoint[],\n options: Partial<StrokeOptions> = {} as Partial<StrokeOptions>\n): number[][] {\n const {\n size = 8,\n thinning = 0.5,\n smoothing = 0.5,\n simulatePressure = true,\n easing = t => t,\n start = {},\n end = {},\n last: isComplete = false,\n } = options\n\n let { streamline = 0.5 } = options\n\n streamline /= 2\n\n const {\n taper: taperStart = 0,\n easing: taperStartEase = t => t * (2 - t),\n } = start\n\n const {\n taper: taperEnd = 0,\n easing: taperEndEase = t => --t * t * t + 1,\n } = end\n\n // The number of points in the array\n const len = points.length\n\n // We can't do anything with an empty array.\n if (len === 0) return []\n\n // The total length of the line\n const totalLength = points[len - 1].runningLength\n\n // Our collected left and right points\n const leftPts: number[][] = []\n const rightPts: number[][] = []\n\n // Previous pressure (start with average of first five pressures)\n let prevPressure = points\n .slice(0, 5)\n .reduce((acc, cur) => (acc + cur.pressure) / 2, points[0].pressure)\n\n // The current radius\n let radius = getStrokeRadius(size, thinning, easing, points[len - 1].pressure)\n\n // Previous vector\n let prevVector = points[0].vector\n\n // Previous left and right points\n let pl = points[0].point\n let pr = pl\n\n // Temporary left and right points\n let tl = pl\n let tr = pr\n\n /*\n Find the outline's left and right points\n\n Iterating through the points and populate the rightPts and leftPts arrays,\n skipping the first and last pointsm, which will get caps later on.\n */\n\n for (let i = 1; i < len - 1; i++) {\n let { point, pressure, vector, distance, runningLength } = points[i]\n\n /*\n Calculate the radius\n\n If not thinning, the current point's radius will be half the size; or\n otherwise, the size will be based on the current (real or simulated)\n pressure. \n */\n\n if (thinning) {\n if (simulatePressure) {\n const rp = min(1, 1 - distance / size)\n const sp = min(1, distance / size)\n pressure = min(1, prevPressure + (rp - prevPressure) * (sp / 2))\n }\n\n radius = getStrokeRadius(size, thinning, easing, pressure)\n } else {\n radius = size / 2\n }\n\n /*\n Apply tapering\n\n If the current length is within the taper distance at either the\n start or the end, calculate the taper strengths. Apply the smaller \n of the two taper strengths to the radius.\n */\n\n const ts =\n runningLength < taperStart\n ? taperStartEase(runningLength / taperStart)\n : 1\n\n const te =\n totalLength - runningLength < taperEnd\n ? taperEndEase((totalLength - runningLength) / taperEnd)\n : 1\n\n radius *= Math.min(ts, te)\n\n /*\n Handle sharp corners\n\n Find the difference (dot product) between the current and next vector.\n If the next vector is at more than a right angle to the current vector,\n draw a cap at the current point.\n */\n\n const nextVector = points[i + 1].vector\n\n const dpr = vec.dpr(vector, nextVector)\n\n if (dpr < 0) {\n const offset = vec.mul(vec.per(prevVector), radius)\n\n for (let t = 0; t < 1; t += 0.2) {\n tr = vec.rotAround(vec.add(point, offset), point, PI * -t)\n tl = vec.rotAround(vec.sub(point, offset), point, PI * t)\n\n rightPts.push(tr)\n leftPts.push(tl)\n }\n\n pl = tl\n pr = tr\n\n continue\n }\n /* \n Add regular points\n\n Project points to either side of the current point, using the\n calculated size as a distance. If a point's distance to the \n previous point on that side greater than the minimum distance\n (or if the corner is kinda sharp), add the points to the side's\n points array.\n */\n\n const offset = vec.mul(vec.per(vec.lrp(nextVector, vector, dpr)), radius)\n\n tl = vec.sub(point, offset)\n tr = vec.add(point, offset)\n\n const alwaysAdd = i === 1 || dpr < 0.25\n const minDistance = Math.pow(\n (runningLength > size ? size : size / 2) * smoothing,\n 2\n )\n\n if (alwaysAdd || vec.dist2(pl, tl) > minDistance) {\n leftPts.push(vec.lrp(pl, tl, streamline))\n pl = tl\n }\n\n if (alwaysAdd || vec.dist2(pr, tr) > minDistance) {\n rightPts.push(vec.lrp(pr, tr, streamline))\n pr = tr\n }\n\n // Set variables for next iteration\n\n prevPressure = pressure\n prevVector = vector\n }\n\n /*\n Drawing caps\n \n Now that we have our points on either side of the line, we need to\n draw caps at the start and end. Tapered lines don't have caps, but\n may have dots for very short lines.\n */\n\n const firstPoint = points[0]\n const lastPoint = points[len - 1]\n const isVeryShort = rightPts.length < 2 || leftPts.length < 2\n\n /* \n Draw a dot for very short or completed strokes\n \n If the line is too short to gather left or right points and if the line is\n not tapered on either side, draw a dot. If the line is tapered, then only\n draw a dot if the line is both very short and complete. If we draw a dot,\n we can just return those points.\n */\n\n if (isVeryShort && (!(taperStart || taperEnd) || isComplete)) {\n let ir = 0\n\n for (let i = 0; i < len; i++) {\n const { pressure, runningLength } = points[i]\n if (runningLength > size) {\n ir = getStrokeRadius(size, thinning, easing, pressure)\n break\n }\n }\n\n const start = vec.sub(\n firstPoint.point,\n vec.mul(\n vec.per(vec.uni(vec.vec(lastPoint.point, firstPoint.point))),\n ir || radius\n )\n )\n\n const dotPts: number[][] = []\n\n for (let t = 0, step = 0.1; t <= 1; t += step) {\n dotPts.push(vec.rotAround(start, firstPoint.point, PI * 2 * t))\n }\n\n return dotPts\n }\n\n /*\n Draw a start cap\n\n Unless the line has a tapered start, or unless the line has a tapered end\n and the line is very short, draw a start cap around the first point. Use\n the distance between the second left and right point for the cap's radius.\n Finally remove the first left and right points. :psyduck:\n */\n\n const startCap: number[][] = []\n\n if (!taperStart && !(taperEnd && isVeryShort)) {\n tr = rightPts[1]\n\n for (let i = 1; i < leftPts.length; i++) {\n if (!vec.isEqual(tr, leftPts[i])) {\n tl = leftPts[i]\n break\n }\n }\n\n if (!vec.isEqual(tr, tl)) {\n const start = vec.sub(\n firstPoint.point,\n vec.mul(vec.uni(vec.vec(tr, tl)), vec.dist(tr, tl) / 2)\n )\n\n for (let t = 0, step = 0.2; t <= 1; t += step) {\n startCap.push(vec.rotAround(start, firstPoint.point, PI * t))\n }\n\n leftPts.shift()\n rightPts.shift()\n }\n }\n\n /*\n Draw an end cap\n\n If the line does not have a tapered end, and unless the line has a tapered\n start and the line is very short, draw a cap around the last point. Finally, \n remove the last left and right points. Otherwise, add the last point. Note\n that This cap is a full-turn-and-a-half: this prevents incorrect caps on \n sharp end turns.\n */\n\n const endCap: number[][] = []\n\n if (!taperEnd && !(taperStart && isVeryShort)) {\n const start = vec.sub(\n lastPoint.point,\n vec.mul(vec.per(lastPoint.vector), radius)\n )\n\n for (let t = 0, step = 0.1; t <= 1; t += step) {\n endCap.push(vec.rotAround(start, lastPoint.point, PI * 3 * t))\n }\n } else {\n endCap.push(lastPoint.point)\n }\n\n /*\n Return the points in the correct windind order: begin on the left side, then \n continue around the end cap, then come back along the right side, and finally \n complete the start cap.\n */\n\n return leftPts.concat(endCap, rightPts.reverse(), startCap)\n}\n\n/**\n * ## getStroke\n * @description Returns a stroke as an array of outline points.\n * @param points An array of points (as `[x, y, pressure]` or `{x, y, pressure}`). Pressure is optional.\n * @param options An (optional) object with options.\n * @param options.size\tThe base size (diameter) of the stroke.\n * @param options.thinning The effect of pressure on the stroke's size.\n * @param options.smoothing\tHow much to soften the stroke's edges.\n * @param options.easing\tAn easing function to apply to each point's pressure.\n * @param options.simulatePressure Whether to simulate pressure based on velocity.\n * @param options.start Tapering and easing function for the start of the line.\n * @param options.end Tapering and easing function for the end of the line.\n * @param options.last Whether to handle the points as a completed stroke.\n */\nexport default function getStroke<\n T extends number[],\n K extends { x: number; y: number; pressure?: number }\n>(points: (T | K)[], options: StrokeOptions = {} as StrokeOptions): number[][] {\n return getStrokeOutlinePoints(getStrokePoints(points, options), options)\n}\n\nexport { StrokeOptions }\n"],"names":["add","A","B","sub","vec","mul","n","div","per","dpr","len","Math","hypot","len2","dist2","uni","dist","rotAround","C","r","s","sin","c","cos","px","py","nx","ny","lrp","t","isEqual","a","b","lerp","y1","y2","mu","clamp","max","min","toPointsArray","points","Array","isArray","map","x","y","pressure","getStrokeRadius","size","thinning","easing","PI","getStrokePoints","options","simulatePressure","streamline","pts","length","push","strokePoints","point","vector","distance","runningLength","i","j","curr","prev","totalLength","getStrokeOutlinePoints","smoothing","start","end","last","isComplete","taper","taperStart","taperStartEase","taperEnd","taperEndEase","leftPts","rightPts","prevPressure","slice","reduce","acc","cur","radius","prevVector","pl","pr","tl","tr","rp","sp","ts","te","nextVector","offset","alwaysAdd","minDistance","pow","firstPoint","lastPoint","isVeryShort","ir","dotPts","step","startCap","shift","endCap","concat","reverse","getStroke"],"mappings":"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"} |
| export interface StrokeOptions { | ||
| size?: number; | ||
| thinning?: number; | ||
| smoothing?: number; | ||
| streamline?: number; | ||
| easing?: (pressure: number) => number; | ||
| simulatePressure?: boolean; | ||
| start?: { | ||
| taper?: number; | ||
| easing?: (distance: number) => number; | ||
| }; | ||
| end?: { | ||
| taper?: number; | ||
| easing?: (distance: number) => number; | ||
| }; | ||
| last?: boolean; | ||
| } | ||
| export interface StrokePoint { | ||
| point: number[]; | ||
| pressure: number; | ||
| vector: number[]; | ||
| distance: number; | ||
| runningLength: number; | ||
| } |
| export declare function lerp(y1: number, y2: number, mu: number): number; | ||
| export declare function clamp(n: number, a: number, b: number): number; | ||
| /** | ||
| * Convert an array of points to the correct format ([x, y, radius]) | ||
| * @param points | ||
| * @returns | ||
| */ | ||
| export declare function toPointsArray<T extends number[], K extends { | ||
| x: number; | ||
| y: number; | ||
| pressure?: number; | ||
| }>(points: (T | K)[]): number[][]; | ||
| /** | ||
| * Compute a radius based on the pressure. | ||
| * @param size | ||
| * @param thinning | ||
| * @param easing | ||
| * @param pressure | ||
| * @returns | ||
| */ | ||
| export declare function getStrokeRadius(size: number, thinning: number, easing: (t: number) => number, pressure?: number): number; | ||
| export declare function withoutDuplicates(pts: number[][]): number[][]; |
| /** | ||
| * Negate a vector. | ||
| * @param A | ||
| */ | ||
| export declare function neg(A: number[]): number[]; | ||
| /** | ||
| * Add vectors. | ||
| * @param A | ||
| * @param B | ||
| */ | ||
| export declare function add(A: number[], B: number[]): number[]; | ||
| /** | ||
| * Subtract vectors. | ||
| * @param A | ||
| * @param B | ||
| */ | ||
| export declare function sub(A: number[], B: number[]): number[]; | ||
| /** | ||
| * Get the vector from vectors A to B. | ||
| * @param A | ||
| * @param B | ||
| */ | ||
| export declare function vec(A: number[], B: number[]): number[]; | ||
| /** | ||
| * Vector multiplication by scalar | ||
| * @param A | ||
| * @param n | ||
| */ | ||
| export declare function mul(A: number[], n: number): number[]; | ||
| /** | ||
| * Vector division by scalar. | ||
| * @param A | ||
| * @param n | ||
| */ | ||
| export declare function div(A: number[], n: number): number[]; | ||
| /** | ||
| * Perpendicular rotation of a vector A | ||
| * @param A | ||
| */ | ||
| export declare function per(A: number[]): number[]; | ||
| /** | ||
| * Dot product | ||
| * @param A | ||
| * @param B | ||
| */ | ||
| export declare function dpr(A: number[], B: number[]): number; | ||
| /** | ||
| * Length of the vector | ||
| * @param A | ||
| */ | ||
| export declare function len(A: number[]): number; | ||
| /** | ||
| * Length of the vector squared | ||
| * @param A | ||
| */ | ||
| export declare function len2(A: number[]): number; | ||
| /** | ||
| * Dist length from A to B squared. | ||
| * @param A | ||
| * @param B | ||
| */ | ||
| export declare function dist2(A: number[], B: number[]): number; | ||
| /** | ||
| * Get normalized / unit vector. | ||
| * @param A | ||
| */ | ||
| export declare function uni(A: number[]): number[]; | ||
| /** | ||
| * Dist length from A to B | ||
| * @param A | ||
| * @param B | ||
| */ | ||
| export declare function dist(A: number[], B: number[]): number; | ||
| /** | ||
| * Mean between two vectors or mid vector between two vectors | ||
| * @param A | ||
| * @param B | ||
| */ | ||
| export declare function med(A: number[], B: number[]): number[]; | ||
| /** | ||
| * Rotate a vector around another vector by r (radians) | ||
| * @param A vector | ||
| * @param C center | ||
| * @param r rotation in radians | ||
| */ | ||
| export declare function rotAround(A: number[], C: number[], r: number): number[]; | ||
| /** | ||
| * Interpolate vector A to B with a scalar t | ||
| * @param A | ||
| * @param B | ||
| * @param t scalar | ||
| */ | ||
| export declare function lrp(A: number[], B: number[], t: number): number[]; | ||
| export declare function isLeft(p1: number[], pc: number[], p2: number[]): number; | ||
| export declare function clockwise(p1: number[], pc: number[], p2: number[]): boolean; | ||
| export declare function isEqual(a: number[], b: number[]): boolean; |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 1 instance in 1 package
21
10.53%267
1.14%9
-35.71%84184
-49.01%12
100%1105
-38.03%4
33.33%1
Infinity%