Gradient.hx 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. package hrt.impl;
  2. import haxe.Int32;
  3. import h3d.mat.Texture;
  4. import h3d.Vector;
  5. typedef ColorStop = {position : Float, color : Int};
  6. @:enum
  7. abstract GradientInterpolation(String) from String to String {
  8. var Linear;
  9. var Cubic;
  10. var Constant;
  11. }
  12. typedef GradientData = {
  13. var stops : Array<ColorStop>;
  14. var resolution : Int;
  15. var isVertical : Bool;
  16. var interpolation: GradientInterpolation;
  17. var colorMode: Int;
  18. };
  19. class Gradient {
  20. public var data : GradientData = {
  21. stops: new Array<ColorStop>(),
  22. resolution: 32,
  23. isVertical: false,
  24. interpolation: Linear,
  25. colorMode: 0,
  26. };
  27. public function new () {
  28. }
  29. public static function getDefaultGradientData() : GradientData {
  30. var data : GradientData = {stops: [{position: 0.0, color:0xFF000000}, {position: 1.0, color:0xFFFFFFFF}], resolution: 64, isVertical : false, interpolation: Linear, colorMode: 0};
  31. return data;
  32. }
  33. public static function evalData(data : GradientData, position : Float, ?outVector : Vector) : Vector {
  34. if (outVector == null) outVector = new Vector();
  35. var i : Int = 0;
  36. while(i < data.stops.length && data.stops[i].position < position) {
  37. i += 1;
  38. }
  39. var firstStopIdx : Int = hxd.Math.iclamp(i-1, 0, data.stops.length-1);
  40. var secondStopIdx : Int = hxd.Math.iclamp(i, 0, data.stops.length-1);
  41. var firstStop = data.stops[firstStopIdx];
  42. var secondStop = data.stops[secondStopIdx];
  43. var c1 : Int = firstStop.color;
  44. var c2 : Int = secondStop.color;
  45. var distance = secondStop.position - firstStop.position;
  46. var offsetFromSecondStop = secondStop.position - position;
  47. var blend = if (distance != 0.0) 1.0 - (offsetFromSecondStop / distance) else 0.0;
  48. blend = hxd.Math.clamp(blend, 0.0, 1.0);
  49. var func = ColorSpace.colorModes[data.colorMode];
  50. var start = func.ARGBToValue(ColorSpace.Color.fromInt(c1), null);
  51. var end = func.ARGBToValue(ColorSpace.Color.fromInt(c2), null);
  52. inline function lerp_angle(a:Float,b:Float,t:Float) : Float {
  53. var diff = (b - a) % 1.0;
  54. var dist = ((2.0 * diff) % 1.0) - diff;
  55. return outVector.x = (a + dist * t + 1.0) % 1.0;
  56. }
  57. switch (data.interpolation) {
  58. case Linear:
  59. outVector.lerp(start, end, blend);
  60. // Patch hue values that need to be lerped around the cercle
  61. if (func.name.charAt(0) == "H") {
  62. outVector.x = lerp_angle(start.x, end.x, blend);
  63. }
  64. case Constant:
  65. outVector.load(start);
  66. case Cubic:
  67. // Honteusement copié de https://github.com/godotengine/godot/blob/c241f1c52386b21cf2df936ee927740a06970db6/scene/resources/gradient.h#L159
  68. var i0 = firstStopIdx-1;
  69. var i3 = secondStopIdx+1;
  70. if (i0 < 0) {
  71. i0 = firstStopIdx;
  72. }
  73. if (i3 >= data.stops.length) {
  74. i3 = data.stops.length-1;
  75. }
  76. var c0 = func.ARGBToValue(ColorSpace.Color.fromInt(data.stops[i0].color), null);
  77. var c3 = func.ARGBToValue(ColorSpace.Color.fromInt(data.stops[i3].color), null);
  78. inline function cubicInterpolate(p_from: Float, p_to: Float, p_pre: Float, p_post: Float, p_weight: Float) {
  79. return 0.5 *
  80. ((p_from * 2.0) +
  81. (-p_pre + p_to) * p_weight +
  82. (2.0 * p_pre - 5.0 * p_from + 4.0 * p_to - p_post) * (p_weight * p_weight) +
  83. (-p_pre + 3.0 * p_from - 3.0 * p_to + p_post) * (p_weight * p_weight * p_weight));
  84. }
  85. outVector.r = cubicInterpolate(start.r, end.r, c0.r, c3.r, blend);
  86. outVector.g = cubicInterpolate(start.g, end.g, c0.g, c3.g, blend);
  87. outVector.b = cubicInterpolate(start.b, end.b, c0.b, c3.b, blend);
  88. outVector.a = cubicInterpolate(start.a, end.a, c0.a, c3.a, blend);
  89. default:
  90. throw "Unknown interpolation mode";
  91. }
  92. var tmp = func.valueToARGB(outVector, null);
  93. ColorSpace.iRGBtofRGB(tmp, outVector);
  94. return outVector;
  95. }
  96. public function eval(position : Float, ?outVector : Vector) : Vector {
  97. return evalData(data, position, outVector);
  98. }
  99. public static function getCache() : Map<Int32, h3d.mat.Texture> {
  100. var engine = h3d.Engine.getCurrent();
  101. var cache : Map<Int32, h3d.mat.Texture> = @:privateAccess engine.resCache.get(Gradient);
  102. if(cache == null) {
  103. cache = new Map<Int32, h3d.mat.Texture>();
  104. @:privateAccess engine.resCache.set(Gradient, cache);
  105. }
  106. return cache;
  107. }
  108. public static function hashCombine(hash : Int32, newValue : Int32) : Int32 {
  109. return hash ^ (newValue * 0x01000193);
  110. }
  111. public static function getDataHash(data : GradientData) : Int32 {
  112. var hash = hashCombine(0, data.resolution);
  113. hash = hashCombine(hash, data.isVertical ? 0 : 1);
  114. // Vieux hack nul
  115. hash = hashCombine(hash, (data.interpolation:String).charCodeAt(0));
  116. hash = hashCombine(hash, (data.interpolation:String).charCodeAt(1));
  117. hash = hashCombine(hash, data.colorMode);
  118. for (stop in data.stops) {
  119. hash = hashCombine(hash, stop.color);
  120. hash = hashCombine(hash, Std.int(stop.position * 214748357));
  121. };
  122. return hash;
  123. }
  124. public static function textureFromData(data : GradientData) : h3d.mat.Texture {
  125. var hash = getDataHash(data);
  126. var cache = getCache();
  127. var entry = cache.get(hash);
  128. if (entry != null)
  129. {
  130. return entry;
  131. }
  132. #if !release
  133. var oldHash = Gradient.getDataHash(data);
  134. #end
  135. function genPixels() {
  136. #if !release
  137. var newHash = Gradient.getDataHash(data);
  138. // If this ever become an issue because we need this feature, we just need to deep copy 'data'
  139. // and use this copy in the genPixels function. But at this moment we consider that it's a bug
  140. if(newHash != oldHash) throw "gradient data has changed between first generation and realloc";
  141. #end
  142. var xScale = data.isVertical ? 0 : 1;
  143. var yScale = 1 - xScale;
  144. var pixels = hxd.Pixels.alloc(data.resolution * xScale + 1 * yScale,1 * xScale + data.resolution * yScale, ARGB);
  145. var vec = new Vector();
  146. for (x in 0...data.resolution) {
  147. evalData(data, x / data.resolution, vec);
  148. pixels.setPixelF(x * xScale,x*yScale, vec);
  149. }
  150. return pixels;
  151. }
  152. var texture = Texture.fromPixels(genPixels(), RGBA);
  153. texture.realloc = function() {
  154. texture.uploadPixels(genPixels());
  155. }
  156. cache.set(hash, texture);
  157. return texture;
  158. }
  159. public function toTexture() : h3d.mat.Texture {
  160. return textureFromData(data);
  161. }
  162. }