/* Copyright 2010 Aaron J. Radke Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package cc.drx /**static helper methods for Lerp use*/ object Lerp{ def pulse(period:Time):Double = ((Date.s*tau/period.s).sin + 1)/2 } /**Lerp generalizes any Segment, Bound, Scale that can be linear interpolated*/ trait Lerp[A]{ /**required methods*/ def lerp(ratio:Double):A /**alias for `lerp` or apply*/ // 2017-07-30("use `lerp` instead since `%` may clobber other features","v0.2.15") // def %(ratio:Double):A = lerp(ratio) // 2017-07-30("use `lerp` instead since `apply` may clobber other mixed in features","v0.2.15") // def apply(ratio:Double):A = lerp(ratio) //==== Lerp /**convenience of the mid value*/ def mid:A = lerp(0.5) def min:A = lerp(0.0) def max:A = lerp(1.0) /**alias for min*/ def start:A = min /**alias for max*/ def end:A = max 2017-07-30("use `mid` instead since 0.5 may not be the mean","v0.2.15") def mean:A = lerp(0.5) def uniform(implicit rand:Rand):A = lerp(rand.uniform(0.0, 1.0)) //get a value uniformly distributed in the bound def normal(implicit rand:Rand):A = lerp(rand.normal(0.5,0.5)) //get a normally distributed value centered around the mean with a variance the bound width def pulse(period:Time):A = lerp(Lerp pulse period) def ramp(t:Time)(implicit t0:Date):A = lerp(t0 ramp t) // potential method names since take has scala iterator like meaning for first set of N //take: conflicts with scala collectdion meaning of first n elements //bag: is short and seems more consitent than sample, and seems like it is more unique, but maybe conflicts histogram bins? //sample: feels like some kind of stochastic process //taste: is more careful than sample : //pick: is spedcfiic //draw: stochastic //draw: stochastic, and overloaded with stohastic //pull: is not as descript //-- sequence interpolated //take //TODO use a better name since take in scale is front based rather than spread based //n [0 1/4 1/2 3/4 1] dt i t0 t //0 [ ] nan //1 [ 1/2 ] nan //2 [0 1] size/1 //3 [0 1/2 1] size/2 //4 [0 1/3 2/3 1] size/2 //n (p = i/(n-1) def take(n:Int):Iterable[A] = if(n <= 0) List() else if(n == 1) List(lerp(0.5)) else (0 to (n-1)).map{i => lerp(i.toDouble/(n-1))} 2017-07-30("use take(N:Int) for a more scala semantic", "v0.2.4") def steps(N:Int):Seq[A] = if(N==0) Seq() else if(N==1) Seq(lerp(0.5)) else (0 to N).map{i => lerp(i.toDouble/N)} def zip[B](xs:Iterable[B]) = take(xs.size) zip xs def toBound(implicit b:Bound.Boundable[A]):Bound[A] = Bound.apply(min, max)(b) } /**LerpInv includes the ability to convert/lookup a specific `type A` value for a interpolation ratio with `ratioOf`*/ //TODO think about matching the ScaleOneWay and Scale form like LerpOneWay and Lerp unless Lerp is more common trait LerpInv[A] extends Lerp[A]{ //---required methods def ratioOf(a:A):Double //---supported methods def normalize(xs:Iterable[A]):Iterable[Double] = xs map ratioOf def contains(a:A):Boolean = {val p = ratioOf(a); p >= 0 && p <= 1} //sat in Bound returns the type // def sat(a:A):A = apply(ratioOf(a).sat(0,1)) /** modulus in a wrapping fashion */ def mobius(x:A):A = { val t = ratioOf(x) % 1.0 lerp(if(t < 0.0) 1.0 + t else t) } //---Tick functions (note these interestingly have to have min and max values and dont' work on the more general Lerp and LerpInv methods //TODO remove this comment if the implemented a min max works here def ticks(n:Int)(implicit t:Tickable[A]):Iterable[A] = t.ticks(n,this) def logTicks(n:Int,base:Double=10)(implicit t:Tickable[A]):Iterable[A] = t.logTicks(n,this,base:Double) //could there be an argument that ticks only occur with format or at least most of the time then use tickValues to get the non formated versions final def ticksWithFormat(n:Int)(implicit t:Tickable[A], f:Format[A]):Iterable[(A,String)] = { val format = t.formatter(n,this) this.ticks(n).map{t => (t, format(t))} } def ticksOn(path:Lerp[Vec], n:Int)(implicit t:Tickable[A]):Iterable[Text] = { //interpolate this domain to the range of the line.bound this.ticksWithFormat(n).map{case (t,f) => Text(f, path lerp ratioOf(t) ) } //tick values, with resolution formatting } private def estimateTickCountOn(path:Segment):Int = { //-- auto select a spacing based on the max,textsize and orientation by width and hight val pathBox = Rect(path.lerp(0),path.lerp(1)) val spacing:Double = if(pathBox.width > pathBox.height) 60 else 20 //TODO auto set rather than hard coded better auto spacing based on font size and data types... path.tickCount(spacing) } /**auto select tick count based on segment length*/ def ticksOn(path:Segment)(implicit t:Tickable[A]):Iterable[Text] = { val n = estimateTickCountOn(path) ticksOn(path,n)(t) } def -->[B](range:LerpInv[B]) = Scale(this, range) def -->[B](range:Lerp[B]) = Scale(this, range) }