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