1 module mutils.timeline.trace_bezier; 2 3 import std.stdio; 4 5 import mutils.container.sorted_vector; 6 import mutils.container.vector; 7 import mutils.linalg.algorithm; 8 import mutils.timeline.utils; 9 10 /** 11 * 12 * Class to get T value interpolated by Bezier Curvein in given time, useful for contigious paths 13 * Class requires at least 3 points to work correctly 14 */ 15 struct TraceBezier(T, alias mixFun=mix){ 16 SortedVector!(DataPoint, "a.time < b.time") data; 17 TimeIndexGetter indexGetter; 18 19 struct DataPoint{ 20 T point=T(0); 21 T suppVec=T(0); 22 float time=0; 23 } 24 25 void add(T point, float time){ 26 size_t i=data.add(DataPoint(point,point,time)); 27 } 28 29 void addAndRecompute(T point, float time){ 30 size_t i=data.add(DataPoint(point,point,time)); 31 recompute(i); 32 } 33 34 void remove(size_t i){ 35 data.remove(i); 36 } 37 38 void removeAndRecompute(size_t i){ 39 data.remove(i); 40 if(i!=data.length){ 41 recompute(i); 42 }else{ 43 recompute(i-1); 44 } 45 } 46 47 T get(float time){ 48 uint[2] ti=indexGetter.index(data[], time); 49 DataPoint curr=data[ti[0]]; 50 DataPoint next=data[ti[1]]; 51 if(ti[0]==ti[1]){ 52 return curr.point; 53 } 54 float blend=(time-curr.time)/(next.time-curr.time); 55 return mix(curr, next, blend); 56 } 57 58 void recompute(size_t i){ 59 if(i==0){ 60 //data[$-1].suppVec=computeSupportingPoint(data[$-2],data[$-1],data[0]); 61 data[0+0].suppVec=computeSupportingPoint(data[0-0],data[0-0],data[1]); 62 data[1+0].suppVec=computeSupportingPoint(data[0-0],data[0+1],data[2]); 63 }else if(i==1){ 64 data[0].suppVec=computeSupportingPoint(data[0],data[0],data[1]); 65 data[1].suppVec=computeSupportingPoint(data[0],data[1],data[2]); 66 data[2].suppVec=computeSupportingPoint(data[1],data[2],data[3]); 67 }else if(i==data.length-1){ 68 data[$-2].suppVec=computeSupportingPoint(data[$-3],data[$-2],data[$-1]); 69 data[$-1].suppVec=computeSupportingPoint(data[$-2],data[$-1],data[$-1]); 70 //data[0-0].suppVec=computeSupportingPoint(data[$-1],data[0+0],data[1-0]); 71 }else if(i==data.length-2){ 72 data[$-3].suppVec=computeSupportingPoint(data[$-4],data[$-3],data[$-2]); 73 data[$-2].suppVec=computeSupportingPoint(data[$-3],data[$-2],data[$-1]); 74 data[$-1].suppVec=computeSupportingPoint(data[$-2],data[$-1],data[$-1]); 75 }else{ 76 data[i-1].suppVec=computeSupportingPoint(data[i-2],data[i-1],data[i]); 77 data[i+0].suppVec=computeSupportingPoint(data[i-1],data[i],data[i+1]); 78 data[i+1].suppVec=computeSupportingPoint(data[i],data[i+1],data[i+2]); 79 } 80 } 81 82 void computeAll(){ 83 assert(data.length>=3); 84 data[0].suppVec=computeSupportingPoint(data[0],data[0],data[1]); 85 foreach(i;1..data.length-1){ 86 data[i].suppVec=computeSupportingPoint(data[i-1],data[i],data[i+1]); 87 } 88 data[$-1].suppVec=computeSupportingPoint(data[$-2],data[$-1],data[$-1]); 89 } 90 91 float totalLength(){ 92 return lengthBetween(data[0].time,data[$-1].time, data.length*3); 93 } 94 95 float totalTime(){ 96 return data[$-1].time; 97 } 98 99 float lengthBetween(float start, float end, size_t precision){ 100 T last=get(start); 101 float dt=(end-start)/precision; 102 float len=0; 103 foreach(i;0..precision){ 104 T p=get(dt*(i+1)); 105 len+=(p-last).length; 106 last=p; 107 } 108 return len; 109 } 110 111 ///Normalize time values based on trace width 112 void normalizeTime(float end){ 113 float timPerLen=end/totalLength(); 114 float start=0; 115 data[0].time=start; 116 foreach(i,ref p;data[1..$]){ 117 p.time=lengthBetween(data[0].time,p.time,(i+1)*3)*timPerLen; 118 } 119 } 120 //private: 121 122 T computeSupportingPoint(DataPoint prev, DataPoint curr, DataPoint next){ 123 float ratio=0.25; 124 T PN=next.point-prev.point;// Previous Current 125 return PN*ratio; 126 } 127 128 T mix(DataPoint curr, DataPoint next, float blend){ 129 T leftSupp=curr.point+curr.suppVec; 130 T righSupp=next.point-next.suppVec; 131 132 T v1=mixFun(curr.point, leftSupp , blend); 133 T v2=mixFun(leftSupp , righSupp , blend); 134 T v3=mixFun(righSupp , next.point, blend); 135 T v4=mixFun(v1,v2, blend); 136 T v5=mixFun(v2,v3, blend); 137 T v6=mixFun(v4,v5, blend); 138 return v6; 139 } 140 bool hasRoundEnds(){ 141 return data[0].point==data[$-1].point; 142 } 143 ///first and last point should be the same 144 void roundEnds(){ 145 data[$-1].point=data[0].point; 146 data[$-1].suppVec=computeSupportingPoint(data[$-2],data[0+0],data[1]); 147 data[0].suppVec=data[$-1].suppVec; 148 } 149 150 } 151 152 153 unittest{ 154 import mutils.linalg.vec; 155 alias vec=Vec!(float,2); 156 alias TraceVec=TraceBezier!(vec); 157 TraceVec trace; 158 trace.add(vec(0,0),0); 159 trace.add(vec(0,1),1); 160 trace.add(vec(1,1),2); 161 trace.add(vec(1,0),3); 162 163 trace.computeAll(); 164 trace.addAndRecompute(vec(0.5,1.5),1.5); 165 }