33// http://www.iscriptdesign.com/?sketch=tutorial/splitbezier
44// http://www.wikiwand.com/en/Hungarian_algorithm
55
6+ import blobs from ".." ;
7+
68let ctx : CanvasRenderingContext2D ;
79
810const animationSpeed = 2 ;
@@ -168,6 +170,7 @@ const approxCurveLength = (a: Point, b: Point): number => {
168170} ;
169171
170172const calcOptimalOffset = ( a : Coord [ ] , b : Coord [ ] ) : number => {
173+ // TODO also reverse
171174 const count = a . length ;
172175 let min = Infinity ;
173176 let minIndex = 0 ;
@@ -185,6 +188,15 @@ const calcOptimalOffset = (a: Coord[], b: Coord[]): number => {
185188 return minIndex ;
186189} ;
187190
191+ const offsetShape = ( offset : number , shape : Point [ ] ) : Point [ ] => {
192+ if ( offset === 0 ) return shape ;
193+ const out : Point [ ] = [ ] ;
194+ for ( let i = 0 ; i < shape . length ; i ++ ) {
195+ out . push ( shape [ ( i + offset ) % shape . length ] ) ;
196+ }
197+ return out ;
198+ } ;
199+
188200const divideShape = ( count : number , points : Point [ ] ) : Point [ ] => {
189201 if ( points . length < 3 ) throw new Error ( "not enough points" ) ;
190202 if ( count < points . length ) throw new Error ( "cannot remove points" ) ;
@@ -314,6 +326,7 @@ const renderShape = (points: Point[]) => {
314326} ;
315327
316328const interpolateBetween = ( percentage : number , a : Point [ ] , b : Point [ ] ) : Point [ ] => {
329+ // TODO when handle length === 0, ignore/modify angle to look nice
317330 if ( a . length !== b . length ) throw new Error ( "shapes have different number of points" ) ;
318331 const points : Point [ ] = [ ] ;
319332 for ( let i = 0 ; i < a . length ; i ++ ) {
@@ -332,6 +345,14 @@ const interpolateBetween = (percentage: number, a: Point[], b: Point[]): Point[]
332345 return points ;
333346} ;
334347
348+ const interpolateBetweenLoop = ( percentage : number , a : Point [ ] , b : Point [ ] ) : Point [ ] => {
349+ if ( percentage < 0.5 ) {
350+ return interpolateBetween ( 2 * percentage , a , b ) ;
351+ } else {
352+ return interpolateBetween ( 2 * percentage - 1 , b , a ) ;
353+ }
354+ } ;
355+
335356const testSplitAt = ( percentage : number ) => {
336357 let points : Point [ ] = [
337358 point ( 0.15 , 0.15 , 135 , 0.1 , 315 , 0.2 ) ,
@@ -366,8 +387,8 @@ const testSplitBy = () => {
366387 renderShape (
367388 splitCurveBy (
368389 i + 1 ,
369- point ( 0.15 , 0.2 + i * 0.06 , 30 , 0.1 , - 30 , 0.1 ) ,
370- point ( 0.45 , 0.2 + i * 0.06 , 135 , 0.1 , 225 , 0.1 ) ,
390+ point ( 0.15 , 0.2 + i * 0.06 , 30 , 0.04 , - 30 , 0.04 ) ,
391+ point ( 0.25 , 0.2 + i * 0.06 , 135 , 0.04 , 225 , 0.04 ) ,
371392 ) ,
372393 ) ;
373394 }
@@ -378,32 +399,93 @@ const testDivideShape = () => {
378399 for ( let i = 0 ; i < count ; i ++ ) {
379400 renderShape (
380401 divideShape ( i + 3 , [
381- point ( 0.6 , 0.2 + i * 0.05 , - 10 , 0.1 , - 45 , 0.03 ) ,
382- point ( 0.7 , 0.2 + i * 0.05 - 0.03 , 180 , 0.03 , 0 , 0.03 ) ,
383- point ( 0.8 , 0.2 + i * 0.05 , - 135 , 0.03 , 170 , 0.1 ) ,
402+ point ( 0.3 , 0.2 + i * 0.05 , - 10 , 0.04 , - 45 , 0.02 ) ,
403+ point ( 0.35 , 0.2 + i * 0.05 - 0.02 , 180 , 0.02 , 0 , 0.02 ) ,
404+ point ( 0.4 , 0.2 + i * 0.05 , - 135 , 0.02 , 170 , 0.04 ) ,
384405 ] ) ,
385406 ) ;
386407 }
387408} ;
388409
389410const testInterpolateBetween = ( percentage : number ) => {
390411 const a = [
391- point ( 0.65 , 0.72 , 135 , 0.05 , - 45 , 0.05 ) ,
392- point ( 0.75 , 0.72 , - 135 , 0.05 , 45 , 0.05 ) ,
393- point ( 0.75 , 0.82 , - 45 , 0.05 , 135 , 0.05 ) ,
394- point ( 0.65 , 0.82 , 45 , 0.05 , 225 , 0.05 ) ,
412+ point ( 0.3 , 0.72 , 135 , 0.05 , - 45 , 0.05 ) ,
413+ point ( 0.4 , 0.72 , - 135 , 0.05 , 45 , 0.05 ) ,
414+ point ( 0.4 , 0.82 , - 45 , 0.05 , 135 , 0.05 ) ,
415+ point ( 0.3 , 0.82 , 45 , 0.05 , 225 , 0.05 ) ,
395416 ] ;
396417 const b = [
397- point ( 0.7 , 0.72 , 180 , 0 , 0 , 0 ) ,
398- point ( 0.75 , 0.77 , - 90 , 0 , 90 , 0 ) ,
399- point ( 0.7 , 0.82 , 360 * 10 , 0 , 180 , 0 ) ,
400- point ( 0.65 , 0.77 , 90 , 0 , - 90 , 0 ) ,
418+ point ( 0.35 , 0.72 , 180 , 0 , 0 , 0 ) ,
419+ point ( 0.4 , 0.77 , - 90 , 0 , 90 , 0 ) ,
420+ point ( 0.35 , 0.82 , 360 * 10 , 0 , 180 , 0 ) ,
421+ point ( 0.3 , 0.77 , 90 , 0 , - 90 , 0 ) ,
401422 ] ;
402- if ( percentage < 0.5 ) {
403- renderShape ( interpolateBetween ( 2 * percentage , a , b ) ) ;
404- } else {
405- renderShape ( interpolateBetween ( 2 * percentage - 1 , b , a ) ) ;
423+ renderShape ( interpolateBetweenLoop ( percentage , a , b ) ) ;
424+ } ;
425+
426+ const testBlobMorph = ( percentage : number ) => {
427+ const a = genBlob ( "a" , 0.6 , 0.6 , 0.3 , { x : 0.5 , y : 0.2 } ) ;
428+ const b = genBlob ( "b" , 1 , 0.6 , 0.3 , { x : 0.5 , y : 0.2 } ) ;
429+
430+ const points = Math . max ( a . length , b . length ) ;
431+ const aNorm = divideShape ( points , a ) ;
432+ const bNorm = divideShape ( points , b ) ;
433+ const offset = calcOptimalOffset ( aNorm , bNorm ) ;
434+ const bOffset = offsetShape ( offset , bNorm ) ;
435+
436+ renderShape ( interpolateBetweenLoop ( percentage , aNorm , bOffset ) ) ;
437+ } ;
438+
439+ const testShapeMorph = ( percentage : number ) => {
440+ const a = genBlob ( "a" , 0.6 , 0.6 , 0.3 , { x : 0.5 , y : 0.5 } ) ;
441+ const b : Point [ ] = [
442+ point ( 0.55 , 0.5 , 0 , 0 , 0 , 0 ) ,
443+ point ( 0.55 , 0.7 , 0 , 0 , 0 , 0 ) ,
444+ point ( 0.75 , 0.7 , 0 , 0 , 0 , 0 ) ,
445+ point ( 0.75 , 0.5 , 0 , 0 , 0 , 0 ) ,
446+ ] ;
447+
448+ const points = Math . max ( a . length , b . length ) ;
449+ const aNorm = divideShape ( points , a ) ;
450+ const bNorm = divideShape ( points , b ) ;
451+ const offset = calcOptimalOffset ( aNorm , bNorm ) ;
452+ const bOffset = offsetShape ( offset , bNorm ) ;
453+ drawInfo ( "offset" , offset )
454+
455+ // renderShape(a);
456+ // renderShape(b);
457+ renderShape ( interpolateBetweenLoop ( percentage , aNorm , bOffset ) ) ;
458+ } ;
459+
460+ const genBlob = (
461+ seed : string ,
462+ complexity : number ,
463+ contrast : number ,
464+ s : number ,
465+ offset : Coord ,
466+ ) : Point [ ] => {
467+ const original = blobs . path ( {
468+ complexity,
469+ contrast,
470+ size : s * size ,
471+ seed,
472+ } ) ;
473+ const out : Point [ ] = [ ] ;
474+ for ( let i = 0 ; i < original . length ; i ++ ) {
475+ const p = original [ i ] ;
476+ if ( ! p . handles ) continue ;
477+ out . push (
478+ point (
479+ p . x / size + offset . x ,
480+ p . y / size + offset . y ,
481+ p . handles . angle + 180 ,
482+ p . handles . in / size ,
483+ p . handles . angle ,
484+ p . handles . out / size ,
485+ ) ,
486+ ) ;
406487 }
488+ return out ;
407489} ;
408490
409491( ( ) => {
@@ -419,11 +501,15 @@ const testInterpolateBetween = (percentage: number) => {
419501 let percentage = animationStart ;
420502 const renderFrame = ( ) => {
421503 ctx . clearRect ( 0 , 0 , canvas . width , canvas . height ) ;
504+
422505 drawInfo ( "percentage" , percentage ) ;
423506 testSplitAt ( percentage ) ;
424507 testSplitBy ( ) ;
425508 testDivideShape ( ) ;
426509 testInterpolateBetween ( percentage ) ;
510+ testBlobMorph ( percentage ) ;
511+ testShapeMorph ( percentage ) ;
512+
427513 percentage += animationSpeed / 1000 ;
428514 percentage %= 1 ;
429515 if ( animationSpeed > 0 ) requestAnimationFrame ( renderFrame ) ;
0 commit comments