Curve.hx 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  1. package hrt.prefab;
  2. using Lambda;
  3. class CurveHandle {
  4. public var dt: Float;
  5. public var dv: Float;
  6. public function new(t, v) {
  7. this.dt = t;
  8. this.dv = v;
  9. }
  10. }
  11. @:enum abstract CurveKeyMode(Int) {
  12. var Aligned = 0;
  13. var Free = 1;
  14. var Linear = 2;
  15. var Constant = 3;
  16. }
  17. class CurveKey {
  18. public var time: Float;
  19. public var value: Float;
  20. public var mode: CurveKeyMode;
  21. public var prevHandle: CurveHandle;
  22. public var nextHandle: CurveHandle;
  23. public function new() {}
  24. }
  25. typedef CurveKeys = Array<CurveKey>;
  26. class Curve extends Prefab {
  27. public var duration : Float = 0.; // TODO: optional?
  28. public var clampMin : Float = 0.;
  29. public var clampMax : Float = 0.;
  30. public var keyMode : CurveKeyMode = Linear;
  31. public var keys : CurveKeys = [];
  32. public function new(?parent) {
  33. super(parent);
  34. this.type = "curve";
  35. }
  36. public override function load(o:Dynamic) {
  37. duration = o.duration;
  38. keys = [];
  39. if(o.keys != null) {
  40. for(k in (o.keys: Array<Dynamic>)) {
  41. var nk = new CurveKey();
  42. nk.time = k.time;
  43. nk.value = k.value;
  44. nk.mode = k.mode;
  45. if(k.prevHandle != null)
  46. nk.prevHandle = new CurveHandle(k.prevHandle.dt, k.prevHandle.dv);
  47. if(k.nextHandle != null)
  48. nk.nextHandle = new CurveHandle(k.nextHandle.dt, k.nextHandle.dv);
  49. keys.push(nk);
  50. }
  51. }
  52. clampMin = o.clampMin;
  53. clampMax = o.clampMax;
  54. if(o.keyMode != null)
  55. keyMode = o.keyMode;
  56. }
  57. public override function save() {
  58. var keysDat = [];
  59. for(k in keys) {
  60. var o = {
  61. time: k.time,
  62. value: k.value,
  63. mode: k.mode
  64. };
  65. if(k.prevHandle != null) Reflect.setField(o, "prevHandle", { dv: k.prevHandle.dv, dt: k.prevHandle.dt });
  66. if(k.nextHandle != null) Reflect.setField(o, "nextHandle", { dv: k.nextHandle.dv, dt: k.nextHandle.dt });
  67. keysDat.push(o);
  68. }
  69. return {
  70. duration: duration,
  71. clampMin: clampMin,
  72. clampMax: clampMax,
  73. keyMode: keyMode,
  74. keys: keysDat,
  75. };
  76. }
  77. static inline function bezier(c0: Float, c1:Float, c2:Float, c3: Float, t:Float) {
  78. var u = 1 - t;
  79. return u * u * u * c0 + c1 * 3 * t * u * u + c2 * 3 * t * t * u + t * t * t * c3;
  80. }
  81. public function findKey(time: Float, tolerance: Float) {
  82. var minDist = tolerance;
  83. var closest = null;
  84. for(k in keys) {
  85. var d = hxd.Math.abs(k.time - time);
  86. if(d < minDist) {
  87. minDist = d;
  88. closest = k;
  89. }
  90. }
  91. return closest;
  92. }
  93. public function addKey(time: Float, ?val: Float, ?mode=null) {
  94. var index = 0;
  95. for(ik in 0...keys.length) {
  96. var key = keys[ik];
  97. if(time > key.time)
  98. index = ik + 1;
  99. }
  100. if(val == null)
  101. val = getVal(time);
  102. var key = new hrt.prefab.Curve.CurveKey();
  103. key.time = time;
  104. key.value = val;
  105. key.mode = mode != null ? mode : (keys[index] != null ? keys[index].mode : keyMode);
  106. keys.insert(index, key);
  107. return key;
  108. }
  109. public function getBounds() {
  110. // TODO: Take bezier handles into account
  111. var ret = new h2d.col.Bounds();
  112. for(k in keys) {
  113. ret.addPos(k.time, k.value);
  114. }
  115. return ret;
  116. }
  117. public function getVal(time: Float) : Float {
  118. switch(keys.length) {
  119. case 0: return 0;
  120. case 1: return keys[0].value;
  121. default:
  122. }
  123. var idx = -1;
  124. for(ik in 0...keys.length) {
  125. var key = keys[ik];
  126. if(time > key.time)
  127. idx = ik;
  128. }
  129. if(idx < 0)
  130. return keys[0].value;
  131. var cur = keys[idx];
  132. var next = keys[idx + 1];
  133. if(next == null || cur.mode == Constant)
  134. return cur.value;
  135. var minT = 0.;
  136. var maxT = 1.;
  137. var maxDelta = 1./ 25.;
  138. inline function sampleTime(t) {
  139. return bezier(
  140. cur.time,
  141. cur.time + (cur.nextHandle != null ? cur.nextHandle.dt : 0.),
  142. next.time + (next.prevHandle != null ? next.prevHandle.dt : 0.),
  143. next.time, t);
  144. }
  145. inline function sampleVal(t) {
  146. return bezier(
  147. cur.value,
  148. cur.value + (cur.nextHandle != null ? cur.nextHandle.dv : 0.),
  149. next.value + (next.prevHandle != null ? next.prevHandle.dv : 0.),
  150. next.value, t);
  151. }
  152. while( maxT - minT > maxDelta ) {
  153. var t = (maxT + minT) * 0.5;
  154. var x = sampleTime(t);
  155. if( x > time )
  156. maxT = t;
  157. else
  158. minT = t;
  159. }
  160. var x0 = sampleTime(minT);
  161. var x1 = sampleTime(maxT);
  162. var dx = x1 - x0;
  163. var xfactor = dx == 0 ? 0.5 : (time - x0) / dx;
  164. var y0 = sampleVal(minT);
  165. var y1 = sampleVal(maxT);
  166. var y = y0 + (y1 - y0) * xfactor;
  167. return y;
  168. }
  169. public function getSum(time: Float) {
  170. var sum = 0.0;
  171. for(ik in 0...keys.length) {
  172. var key = keys[ik];
  173. if(time < key.time)
  174. break;
  175. if(ik == 0 && key.time > 0) {
  176. // Account for start of curve
  177. sum += key.time * key.value;
  178. }
  179. var nkey = keys[ik + 1];
  180. if(nkey != null) {
  181. if(time > nkey.time) {
  182. // Full interval
  183. sum += key.value * (nkey.time - key.time);
  184. if(key.mode != Constant)
  185. sum += 0.5 * (nkey.time - key.time) * (nkey.value - key.value);
  186. }
  187. else {
  188. // Split interval
  189. sum += key.value * (time - key.time);
  190. if(key.mode != Constant)
  191. sum += 0.5 * (time - key.time) * hxd.Math.lerp(key.value, nkey.value, (time - key.time) / (nkey.time - key.time));
  192. }
  193. }
  194. else {
  195. sum += key.value * (time - key.time);
  196. }
  197. }
  198. return sum;
  199. }
  200. public function sample(numPts: Int) {
  201. var vals = [];
  202. for(i in 0...numPts) {
  203. var v = getVal(duration * i/(numPts-1));
  204. vals.push(v);
  205. }
  206. return vals;
  207. }
  208. #if editor
  209. override function getHideProps() : HideProps {
  210. return { icon : "paint-brush", name : "Curve" };
  211. }
  212. #end
  213. public static function getCurve(parent : Prefab, name: String, onlyEnabled=true) {
  214. for(c in parent.children) {
  215. if(onlyEnabled && !c.enabled) continue;
  216. if(c.name != name) continue;
  217. var curve = c.to(Curve);
  218. if(curve == null) continue;
  219. return curve;
  220. }
  221. return null;
  222. }
  223. public static function getCurves(parent: Prefab, prefix: String) {
  224. var ret = [];
  225. for(c in parent.children) {
  226. if(!c.enabled) continue;
  227. if(c.name.split(".")[0] != prefix)
  228. continue;
  229. var curve = c.to(Curve);
  230. if(curve == null) continue;
  231. ret.push(curve);
  232. }
  233. return ret;
  234. }
  235. public static function getGroups(curves: Array<Curve>) {
  236. var groups : Array<{name: String, items: Array<Curve>}> = [];
  237. for(c in curves) {
  238. var prefix = c.name.split(".")[0];
  239. var g = groups.find(g -> g.name == prefix);
  240. if(g == null) {
  241. groups.push({
  242. name: prefix,
  243. items: [c]
  244. });
  245. }
  246. else {
  247. g.items.push(c);
  248. }
  249. }
  250. return groups;
  251. }
  252. static inline function findCurve(curves: Array<Curve>, suffix: String) {
  253. return curves.find(c -> StringTools.endsWith(c.name, suffix));
  254. }
  255. public static function getVectorValue(curves: Array<Curve>, defVal: Float=0.0, scale: Float=1.0) : hrt.prefab.fx.Value {
  256. inline function find(s) {
  257. return findCurve(curves, s);
  258. }
  259. var x = find(".x");
  260. var y = find(".y");
  261. var z = find(".z");
  262. var w = find(".w");
  263. inline function curveOrVal(c: Curve, defVal: Float) : hrt.prefab.fx.Value {
  264. return c != null ? (scale != 1.0 ? VCurveScale(c, scale) : VCurve(c)) : VConst(defVal);
  265. }
  266. return VVector(
  267. curveOrVal(x, defVal),
  268. curveOrVal(y, defVal),
  269. curveOrVal(z, defVal),
  270. curveOrVal(w, 1.0));
  271. }
  272. public static function getColorValue(curves: Array<Curve>) : hrt.prefab.fx.Value {
  273. inline function find(s) {
  274. return findCurve(curves, s);
  275. }
  276. var r = find(".r");
  277. var g = find(".g");
  278. var b = find(".b");
  279. var a = find(".a");
  280. var h = find(".h");
  281. var s = find(".s");
  282. var l = find(".l");
  283. if(h != null || s != null || l != null) {
  284. return VHsl(
  285. h != null ? VCurve(h) : VConst(0.0),
  286. s != null ? VCurve(s) : VConst(1.0),
  287. l != null ? VCurve(l) : VConst(1.0),
  288. a != null ? VCurve(a) : VConst(1.0));
  289. }
  290. if(a != null && r == null && g == null && b == null)
  291. return VCurve(a);
  292. return VVector(
  293. r != null ? VCurve(r) : VConst(1.0),
  294. g != null ? VCurve(g) : VConst(1.0),
  295. b != null ? VCurve(b) : VConst(1.0),
  296. a != null ? VCurve(a) : VConst(1.0));
  297. }
  298. static var _ = Library.register("curve", Curve);
  299. }