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 }