gt:                     double[] uv = quad.invBilinear(u, v);
context: /*
   * This file is part of Gaia Sky, which is released under the Mozilla Public License 2.0.
   * See the file LICENSE.md in the project root for full license details.
   */
  
  package gaiasky.util.gdx.loader;
  
  import com.badlogic.gdx.files.FileHandle;
  import com.badlogic.gdx.graphics.GL30;
  import com.badlogic.gdx.graphics.Pixmap;
  import com.badlogic.gdx.graphics.Pixmap.Format;
  import com.badlogic.gdx.graphics.TextureData;
  import com.badlogic.gdx.graphics.glutils.FloatTextureData;
  import com.badlogic.gdx.utils.GdxRuntimeException;
  import gaiasky.util.Logger;
  import gaiasky.util.math.MathUtilsd;
  
  import java.nio.ByteBuffer;
  import java.nio.FloatBuffer;
  import java.util.ArrayList;
  import java.util.List;
  import java.util.function.Function;
  import java.util.stream.Collectors;
  
  public class PFMReader {
      private static Logger.Log logger = Logger.getLogger(PFMReader.class);
  
      static public TextureData readPFMTextureData(FileHandle file, boolean invert) {
  
          try {
              PortableFloatMap pfm = new PortableFloatMap(file.file());
              float[] data = pfm.pixels;
              int width = pfm.width;
              int height = pfm.height;
              if (invert)
                  data = invertWarp(data, width, height);
  
              FloatTextureData td = new FloatTextureData(width, height, GL30.GL_RGB16F, GL30.GL_RGB, GL30.GL_FLOAT, false);
              td.prepare();
              FloatBuffer buff = td.getBuffer();
              buff.put(data);
  
              return td;
          } catch (Exception e) {
              throw new GdxRuntimeException("Couldn't read PFM file '" + file + "'", e);
          } finally {
          }
      }
  
      static public PFMData readPFMData(FileHandle file, boolean invert) {
          try {
              PortableFloatMap pfm = new PortableFloatMap(file.file());
              float[] data = pfm.pixels;
              int width = pfm.width;
              int height = pfm.height;
              //data = generateMapping(width, height, val -> val * val);
              if (invert) {
                  data = invertWarp(data, width, height);
              } else {
                  // Normalize
                  for (int i = 0; i < data.length; i++)
                      data[i] = (float) nor(data[i]);
              }
  
              return new PFMData(data, width, height);
          } catch (Exception e) {
              throw new GdxRuntimeException("Couldn't read PFM file '" + file + "'", e);
          } finally {
          }
      }
  
      static public Pixmap readPFMPixmap(FileHandle file, boolean invert) {
          try {
              PortableFloatMap pfm = new PortableFloatMap(file.file());
              float[] data = pfm.pixels;
              int width = pfm.width;
              int height = pfm.height;
              //int width = 100;
              //int height = 100;
              //float[] floatData = generateMapping(width, height, val -> val);
              if (invert)
                  data = invertWarp(data, width, height);
              int totalSize = pfm.pixels.length;
              //int totalSize = width * height * 3;
  
              // Convert to Pixmap
              Format format = Format.RGB888;
              Pixmap pixmap = new Pixmap(width, height, format);
  
              ByteBuffer pixelBuf = pixmap.getPixels();
              pixelBuf.position(0);
              pixelBuf.limit(pixelBuf.capacity());
  
              for (int i = 0; i < totalSize; i++) {
                  float f = data[i];
                  byte b;
                  if (Float.isNaN(f)) {
                      b = (byte) 0;
                  } else {
                      f = (float) ((invert ? f : nor(f)) * 255.0);
                      b = (byte) f;
                  }
  
                  pixelBuf.put(b);
              }
  
              pixelBuf.position(0);
              pixelBuf.limit(pixelBuf.capacity());
  
              return pixmap;
          } catch (Exception e) {
              throw new GdxRuntimeException("Couldn't read PFM file '" + file + "'", e);
          } finally {
          }
      }
  
      private static double nor(double value) {
          return MathUtilsd.clamp(value + 0.5d, 0d, 1d);
          //return value;
      }
  
      static public PFMData constructPFMData(int width, int height, Function<Float, Float> f) {
          try {
              float[] data = generateMapping(width, height, f);
              return new PFMData(data, width, height);
          } catch (Exception e) {
              throw new GdxRuntimeException("Couldn't construct PFM data", e);
          } finally {
          }
      }
  
      private static float[] generateMapping(int w, int h, Function<Float, Float> f) {
          float[] out = new float[w * h * 3];
  
          for (int j = 0; j < h; j++) {
              for (int i = 0; i < w; i++) {
                  float u = (float) i / (float) (w - 1);
                  float v = (float) j / (float) (h - 1);
  
                  // Store this pixel's position at end location
                  out[(w * j + i) * 3 + 0] = f.apply(u);
                  out[(w * j + i) * 3 + 1] = f.apply(v);
                  out[(w * j + i) * 3 + 2] = Float.NaN;
              }
          }
          return out;
      }
  
      public static List<Quad> generateMesh(float[] d, int w, int h) {
          List<Quad> mesh = new ArrayList<>();
          for (int j = 0; j < h - 1; j++) {
              for (int i = 0; i < w - 1; i++) {
                  double u = i / (w - 1d);
                  double v = j / (h - 1d);
                  double[] origuv = new double[] { u, v };
  
                  int bl = (w * j + i) * 3;
                  int br = (w * j + i + 1) * 3;
                  int tl = (w * (j + 1) + i) * 3;
                  int tr = (w * (j + 1) + i + 1) * 3;
                  double[] positions = new double[] { nor(d[bl]), nor(d[bl + 1]), nor(d[br]), nor(d[br + 1]), nor(d[tr]), nor(d[tr + 1]), nor(d[tl]), nor(d[tl + 1]) };
  
                  Quad quad = new Quad(positions, origuv);
                  mesh.add(quad);
              }
          }
          return mesh;
      }
  
      /**
       * Inverts the warp function. The mapping must be invertible, i.e. no folds must be
       * present.
       *
       * @param d The data
       * @param w The source width
       * @param h The source height
       * @return
       */
      private static float[] invertWarp(final float[] d, int w, int h) throws RuntimeException {
          // Create transformed mesh
          List<Quad> mesh = generateMesh(d, w, h);
  
          double du = 1d / (w - 1d);
          double dv = 1d / (h - 1d);
  
          boolean warnedFolds = false;
          boolean warnedRange = false;
          // Go over every pixel in final image and map it to original
          float[] out = new float[d.length];
          for (int j = 0; j < h; j++) {
              for (int i = 0; i < w; i++) {
                  int p = (w * j + i) * 3;
                  double u = i / (w - 1d);
                  double v = j / (h - 1d);
  
                  List<Quad> matches = mesh.stream().filter(quad -> quad.containsAlt02(u, v)).collect(Collectors.toList());
                  if (matches.size() == 0) {
                      // Black
                      out[p + 0] = 0;
                      out[p + 1] = 0;
                      out[p + 2] = Float.NaN;
                      if (!warnedRange) {
                          logger.warn("WARN: The geometry warp forward function does not cover the whole image!");
                          warnedRange = true;
                      }
                  } else {
                      if (matches.size() > 1 && !warnedFolds) {
                          // It has folds, warn
                          logger.warn("WARN: The geometry warp forward function has folds!");
                          warnedFolds = true;
                      }
                      // Take first and do inverse bilinear interpolation
                      Quad quad = matches.get(0);
  