|
| 1 | +### |
| 2 | +Abstract scalar interpolation class which provides common functionality for all interpolators |
| 3 | +
|
| 4 | +Subclasses must override interpolate(). |
| 5 | +### |
| 6 | + |
| 7 | + |
| 8 | +###Constants (these are accessible by Smooth.WHATEVER in user space)### |
| 9 | +Enum = |
| 10 | + ###Interpolation methods### |
| 11 | + METHOD_NEAREST: 0 #Rounds to nearest whole index |
| 12 | + METHOD_LINEAR: 1 # Default: linear interpolation |
| 13 | + METHOD_CUBIC: 2 |
| 14 | + |
| 15 | + ###Input clipping types### |
| 16 | + CLIP_CLAMP: 0 # Default: clamp to [0, arr.length-1] |
| 17 | + CLIP_ZERO: 1 # When out of bounds, clip to zero |
| 18 | + CLIP_PERIODIC: 2 # Repeat the array infinitely in either direction |
| 19 | + CLIP_MIRROR: 3 # Repeat infinitely in either direction, flipping each time |
| 20 | + |
| 21 | + |
| 22 | +defaultConfig = |
| 23 | + method: Enum.METHOD_LINEAR |
| 24 | + clip: Enum.CLIP_CLAMP |
| 25 | + |
| 26 | + |
| 27 | + |
| 28 | +###Index clipping functions### |
| 29 | +clipClamp = (i, n) -> Math.max 0, Math.min i, n - 1 |
| 30 | + |
| 31 | +clipPeriodic = (i, n) -> |
| 32 | + i = i % n #wrap |
| 33 | + i += n if i < 0 #if negative, wrap back around |
| 34 | + i |
| 35 | + |
| 36 | +clipMirror = (i, n) -> |
| 37 | + period = 2*(n - 1) #period of index mirroring function |
| 38 | + i = clipPeriodic i, period |
| 39 | + i = period - i if i > n - 1 #flip when out of bounds |
| 40 | + i |
| 41 | + |
| 42 | +getFraction = (x) -> x - Math.floor x |
| 43 | + |
| 44 | +class AbstractInterpolator |
| 45 | + |
| 46 | + constructor: (@array, config) -> |
| 47 | + @length = @array.length #cache length |
| 48 | + |
| 49 | + #Set the clipping helper method |
| 50 | + @clipHelper = switch config.clip |
| 51 | + when Enum.CLIP_CLAMP |
| 52 | + @clipHelperClamp |
| 53 | + when Enum.CLIP_ZERO |
| 54 | + @clipHelperZero |
| 55 | + when Enum.CLIP_PERIODIC |
| 56 | + @clipHelperPeriodic |
| 57 | + when Enum.CLIP_MIRROR |
| 58 | + @clipHelperMirror |
| 59 | + else |
| 60 | + err = new Error |
| 61 | + err.message = "The clipping type #{config.clip} is invalid." |
| 62 | + |
| 63 | + # Get input array value at i, applying the clipping method |
| 64 | + getClippedInput: (i) -> |
| 65 | + #Normal behavior for indexes within bounds |
| 66 | + if 0 <= i < @length |
| 67 | + @array[i] |
| 68 | + else |
| 69 | + @clipHelper i |
| 70 | + |
| 71 | + clipHelperClamp: (i) -> @array[clipClamp i, @length] |
| 72 | + |
| 73 | + clipHelperZero: (i) -> 0 |
| 74 | + |
| 75 | + clipHelperPeriodic: (i) -> @array[clipPeriodic i, @length] |
| 76 | + |
| 77 | + clipHelperMirror: (i) -> @array[clipMirror i, @length] |
| 78 | + |
| 79 | + interpolate: (t) -> |
| 80 | + err = new Error |
| 81 | + err.message = 'Subclasses of AbstractInterpolator must override the interpolate() method.' |
| 82 | + throw err |
| 83 | + |
| 84 | + |
| 85 | +#Nearest neighbor interpolator (round to whole index) |
| 86 | +class NearestInterpolator extends AbstractInterpolator |
| 87 | + interpolate: (t) -> @getClippedInput Math.round t |
| 88 | + |
| 89 | + |
| 90 | +#Linear interpolator (first order Bezier) |
| 91 | +class LinearInterpolator extends AbstractInterpolator |
| 92 | + interpolate: (t) -> |
| 93 | + i = Math.floor t |
| 94 | + a = @getClippedInput i |
| 95 | + b = @getClippedInput i+1 |
| 96 | + t = getFraction t |
| 97 | + return (1-t)*a + (t)*b |
| 98 | + |
| 99 | + |
| 100 | + |
| 101 | + |
| 102 | +#Extract a column from a two dimensional array |
| 103 | +getColumn = (arr, i) -> (row[i] for row in arr) |
| 104 | + |
| 105 | +Smooth = (arr, config = {}) -> |
| 106 | + config[k] ?= v for own k,v of defaultConfig #fill in defaults |
| 107 | + |
| 108 | + #Get the interpolator class according to the configuration |
| 109 | + interpolatorClass = switch config.method |
| 110 | + when Enum.METHOD_NEAREST then NearestInterpolator |
| 111 | + when Enum.METHOD_LINEAR then LinearInterpolator |
| 112 | + when Enum.METHOD_CUBIC then CubicInterpolator |
| 113 | + else |
| 114 | + err = new Error |
| 115 | + err.message = "The interpolation method #{config.method} is invalid." |
| 116 | + |
| 117 | + #Make sure there's at least one element in the input array |
| 118 | + if not arr.length |
| 119 | + err = new Error |
| 120 | + err.message = 'Array must have at least one element.' |
| 121 | + |
| 122 | + #See what type of data we're dealing with |
| 123 | + dataType = Object.prototype.toString.call arr[0] |
| 124 | + switch dataType |
| 125 | + when '[object Number]' #scalar |
| 126 | + interpolator = new interpolatorClass arr, config |
| 127 | + return (t) -> interpolator.interpolate t |
| 128 | + |
| 129 | + when '[object Array]' # vector |
| 130 | + interpolators = (new interpolatorClass(getColumn(arr, i), config) for i in [0...arr[0].length]) |
| 131 | + return (t) -> (interpolator.interpolate(t) for interpolator in interpolators) |
| 132 | + |
| 133 | + else |
| 134 | + err = new Error |
| 135 | + err.message = 'Invalid element type: #{dataType}' |
| 136 | + |
| 137 | + |
| 138 | + |
| 139 | +#Copy enums to Smooth |
| 140 | +Smooth[k] = v for own k,v of Enum |
| 141 | + |
| 142 | + |
| 143 | +root = exports ? window |
| 144 | +root.Smooth = Smooth |
0 commit comments