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 }