11// http://www.cad.zju.edu.cn/home/zhx/papers/PoissonMorphing.pdf
22// https://medium.com/@adrian_cooney /bezier-interpolation-13b68563313a
33// http://www.iscriptdesign.com/?sketch=tutorial/splitbezier
4+ // http://www.wikiwand.com/en/Hungarian_algorithm
45
56let ctx : CanvasRenderingContext2D ;
67
@@ -15,7 +16,7 @@ const infoSpacing = 20;
1516const pointSize = 2 ;
1617const size = 1000 ;
1718
18- interface Coordinates {
19+ interface Coord {
1920 // Horizontal distance towards the right from the left edge of the canvas.
2021 x : number ;
2122 // Vertical distance downwards from the top of the canvas.
@@ -61,11 +62,11 @@ const interpolate = (...keyframes: Keyframe[]) => {
6162 // - Output using generator?
6263} ;
6364
64- export const rad = ( deg : number ) => {
65+ const rad = ( deg : number ) => {
6566 return ( deg / 360 ) * 2 * Math . PI ;
6667} ;
6768
68- export const distance = ( a : Coordinates , b : Coordinates ) : number => {
69+ const distance = ( a : Coord , b : Coord ) : number => {
6970 return Math . sqrt ( ( a . x - b . x ) ** 2 + ( a . y - b . y ) ** 2 ) ;
7071} ;
7172
@@ -85,14 +86,14 @@ const copyPoint = (p: Point): Point => ({
8586 handleOut : { ...p . handleOut } ,
8687} ) ;
8788
88- const expandHandle = ( origin : Coordinates , handle : Handle ) : Coordinates => {
89+ const expandHandle = ( origin : Coord , handle : Handle ) : Coord => {
8990 return {
9091 x : origin . x + handle . length * Math . cos ( handle . angle ) ,
9192 y : origin . y + handle . length * Math . sin ( handle . angle ) ,
9293 } ;
9394} ;
9495
95- const collapseHandle = ( origin : Coordinates , handle : Coordinates ) : Handle => {
96+ const collapseHandle = ( origin : Coord , handle : Coord ) : Handle => {
9697 const dx = handle . x - origin . x ;
9798 const dy = - handle . y + origin . y ;
9899 let angle = Math . atan2 ( dy , dx ) ;
@@ -102,7 +103,7 @@ const collapseHandle = (origin: Coordinates, handle: Coordinates): Handle => {
102103 } ;
103104} ;
104105
105- const drawLine = ( a : Coordinates , b : Coordinates , style : string ) => {
106+ const drawLine = ( a : Coord , b : Coord , style : string ) => {
106107 const backupStrokeStyle = ctx . strokeStyle ;
107108 ctx . beginPath ( ) ;
108109 ctx . moveTo ( a . x , a . y ) ;
@@ -112,7 +113,7 @@ const drawLine = (a: Coordinates, b: Coordinates, style: string) => {
112113 ctx . strokeStyle = backupStrokeStyle ;
113114} ;
114115
115- const drawPoint = ( p : Coordinates , style : string ) => {
116+ const drawPoint = ( p : Coord , style : string ) => {
116117 const backupFillStyle = ctx . fillStyle ;
117118 ctx . beginPath ( ) ;
118119 ctx . arc ( p . x , p . y , pointSize , 0 , 2 * Math . PI ) ;
@@ -133,10 +134,28 @@ const drawInfo = (() => {
133134 } ;
134135} ) ( ) ;
135136
136- const splitLine = ( percentage : number , a : Coordinates , b : Coordinates ) : Coordinates => {
137+ const split = ( percentage : number , a : number , b : number ) : number => {
138+ return a + percentage * ( b - a ) ;
139+ } ;
140+
141+ const splitAngle = ( percentage : number , a : number , b : number ) : number => {
142+ const tau = Math . PI * 2 ;
143+ let aNorm = ( ( a % tau ) + tau ) % tau ;
144+ let bNorm = ( ( b % tau ) + tau ) % tau ;
145+ if ( Math . abs ( aNorm - bNorm ) > Math . PI ) {
146+ if ( aNorm < bNorm ) {
147+ aNorm += tau ;
148+ } else {
149+ bNorm += tau ;
150+ }
151+ }
152+ return split ( percentage , aNorm , bNorm ) ;
153+ } ;
154+
155+ const splitLine = ( percentage : number , a : Coord , b : Coord ) : Coord => {
137156 return {
138- x : a . x + percentage * ( b . x - a . x ) ,
139- y : a . y + percentage * ( b . y - a . y ) ,
157+ x : split ( percentage , a . x , b . x ) ,
158+ y : split ( percentage , a . y , b . y ) ,
140159 } ;
141160} ;
142161
@@ -226,7 +245,7 @@ const splitCurveAt = (percentage: number, a: Point, b: Point): [Point, Point, Po
226245 const f = splitLine ( percentage , aHandle , bHandle ) ;
227246 const g = splitLine ( percentage , cHandle , f ) ;
228247 const h = splitLine ( 1 - percentage , eHandle , f ) ;
229- const dCoordinates = splitLine ( percentage , g , h ) ;
248+ const dCoord = splitLine ( percentage , g , h ) ;
230249
231250 if ( debugBezier ) {
232251 drawLine ( b , bHandle , debugBezierColor ) ;
@@ -236,17 +255,17 @@ const splitCurveAt = (percentage: number, a: Point, b: Point): [Point, Point, Po
236255 drawLine ( eHandle , f , debugBezierColor ) ;
237256 drawLine ( g , h , debugBezierColor ) ;
238257 if ( ! debugHandles ) {
239- drawPoint ( dCoordinates , debugBezierColor ) ;
240- drawLine ( dCoordinates , g , debugBezierColor ) ;
241- drawLine ( dCoordinates , h , debugBezierColor ) ;
258+ drawPoint ( dCoord , debugBezierColor ) ;
259+ drawLine ( dCoord , g , debugBezierColor ) ;
260+ drawLine ( dCoord , h , debugBezierColor ) ;
242261 }
243262 }
244263
245264 const d : Point = {
246- x : dCoordinates . x ,
247- y : dCoordinates . y ,
248- handleIn : collapseHandle ( dCoordinates , g ) ,
249- handleOut : collapseHandle ( dCoordinates , h ) ,
265+ x : dCoord . x ,
266+ y : dCoord . y ,
267+ handleIn : collapseHandle ( dCoord , g ) ,
268+ handleOut : collapseHandle ( dCoord , h ) ,
250269 } ;
251270 return [ c , d , e ] ;
252271} ;
@@ -276,6 +295,25 @@ const renderShape = (points: Point[]) => {
276295 }
277296} ;
278297
298+ const interpolateBetween = ( percentage : number , a : Point [ ] , b : Point [ ] ) : Point [ ] => {
299+ if ( a . length !== b . length ) throw new Error ( "shapes have different number of points" ) ;
300+ const points : Point [ ] = [ ] ;
301+ for ( let i = 0 ; i < a . length ; i ++ ) {
302+ points . push ( {
303+ ...splitLine ( percentage , a [ i ] , b [ i ] ) ,
304+ handleIn : {
305+ angle : splitAngle ( percentage , a [ i ] . handleIn . angle , b [ i ] . handleIn . angle ) ,
306+ length : split ( percentage , a [ i ] . handleIn . length , b [ i ] . handleIn . length ) ,
307+ } ,
308+ handleOut : {
309+ angle : splitAngle ( percentage , a [ i ] . handleOut . angle , b [ i ] . handleOut . angle ) ,
310+ length : split ( percentage , a [ i ] . handleOut . length , b [ i ] . handleOut . length ) ,
311+ } ,
312+ } ) ;
313+ }
314+ return points ;
315+ } ;
316+
279317const testSplitAt = ( percentage : number ) => {
280318 let points : Point [ ] = [
281319 point ( 0.15 , 0.15 , 135 , 0.1 , 315 , 0.2 ) ,
@@ -330,6 +368,26 @@ const testDivideShape = () => {
330368 }
331369} ;
332370
371+ const testInterpolateBetween = ( percentage : number ) => {
372+ const a = [
373+ point ( 0.65 , 0.72 , 135 , 0.05 , - 45 , 0.05 ) ,
374+ point ( 0.75 , 0.72 , - 135 , 0.05 , 45 , 0.05 ) ,
375+ point ( 0.75 , 0.82 , - 45 , 0.05 , 135 , 0.05 ) ,
376+ point ( 0.65 , 0.82 , 45 , 0.05 , 225 , 0.05 ) ,
377+ ] ;
378+ const b = [
379+ point ( 0.7 , 0.72 , 180 , 0 , 0 , 0 ) ,
380+ point ( 0.75 , 0.77 , - 90 , 0 , 90 , 0 ) ,
381+ point ( 0.7 , 0.82 , 360 * 10 , 0 , 180 , 0 ) ,
382+ point ( 0.65 , 0.77 , 90 , 0 , - 90 , 0 ) ,
383+ ] ;
384+ if ( percentage < 0.5 ) {
385+ renderShape ( interpolateBetween ( 2 * percentage , a , b ) ) ;
386+ } else {
387+ renderShape ( interpolateBetween ( 2 * percentage - 1 , b , a ) ) ;
388+ }
389+ } ;
390+
333391( ( ) => {
334392 const canvas = document . createElement ( "canvas" ) ;
335393 canvas . width = size ;
@@ -347,6 +405,7 @@ const testDivideShape = () => {
347405 testSplitAt ( percentage ) ;
348406 testSplitBy ( ) ;
349407 testDivideShape ( ) ;
408+ testInterpolateBetween ( percentage ) ;
350409 percentage += animationSpeed / 1000 ;
351410 percentage %= 1 ;
352411 if ( animationSpeed > 0 ) requestAnimationFrame ( renderFrame ) ;
0 commit comments