// Green Gandalf's perlin noise shader v4.0d - seamless and basic versions
//
//   ** This is still work in progress - so be warned!
//
// This is designed to work with a simple 2x2 quad with corners clockwise from top left:
// (-1, 1), (1, 1), (1, -1), (-1, -1) and corresponding UV coordinates (0, 0),  (1, 0), (0, 1), (1, 1).
//
// Intended to be used with the DBPro set camera to image command.
//
// Creates a Perlin noise style image with up to 8 octaves (numbered 0 to 7).
// Contains two techniques:
//
//    perlinNoiseBasic - produces a single image that is not tileable, i.e. it is not seamless
//                       (it is slightly faster than theseamless version next)
//    perlinNoiseSeamless - produces a single image that tiles seamlessly and includes an
//                          additional "seed" feature that allows you to create different
//                          images which tile together (as in endless terrain).

// Includes two RNGs:
//
//    random1 - one I borrowed and modified from this site:
//                     http://obge.paradice-insight.us/wiki/Includes_(Effects)
//              unfortunately that site doesn't seem to be available anymore - but it's the only
//              reference I have :(
//    random2 - my own simpler ad hoc RNG - my tests suggest that random1 is slightly slower
//              but gives slightly better results).
//
// Suggestions for a better random generator welcome.
//
// Created 16 August 2014, modified 24 August 2014.
//
// *** Please note: This shader might not run correctly in the current version of Dark Shader.
// ***              I've no idea why. However, the compilation errors seem to be correct which
// ***              is what I use Dark Shader for.
// ***              The good news is that the shader seems to load and run correctly in DBPro
// ***              although the initial loading time can be slow.

float4x4 mWVP : WorldViewProjection;

// Dark Shader style tweakables have been removed from this version since they can't be used
// in Dark Shader with this shader.

float boundFactor = 2.0; // not very sure what would be the best value for this - but this choice
                         // seems to work
float octaveDivisor = 0.6; // make small adjustments to this to change the appearance of the clouds
                           // should probably be in the range 0.4-0.8
int maxOctaves = 8; // don't change this - as the references are hard-coded in the shader

float4 list1 = {1.0, 1.0, 1.0, 1.0}; // used for initialising first four octaves (Dark Shader bug :( )
float4 list2 = {1.0, 1.0, 1.0, 1.0}; // used for initialising last four octaves
int octaveList[8] = {1, 1, 1, 1, 1, 1, 1, 1}; // list of which octaves to use - use all by default

// the following seeds don't quite work as intended - no idea why
float2 seed = {0.0, 0.0};    // this enables you to get different realisations of the image
float2 seed2 = {0.61, 0.37}; // this enables you to get different X and Y components for the tangent slopes
float2 corner00 = {0.0, 0.0}; // to simplify referencing the four corners of each tile
float2 corner01 = {0.0, 1.0};
float2 corner10 = {1.0, 0.0};
float2 corner11 = {1.0, 1.0};

float2 invSourceTextureWidth = {1.0/512.0, 1.0/512.0}; // these should be initialised in the application
                                                       // to match your target image dimensions

struct VSInput
{ float4 pos    : position;
  float2 UV     : texcoord0;
};

struct VSOutput
{ float4 pos    : position;
  float2 UV     : texcoord0;
};

struct PSInput
{ float2 UV     : texcoord0;
};

struct PSOutput { float4 colour : color; };

float random1(in float2 p)
// simplified version of rand_1_5 from this page: http://obge.paradice-insight.us/wiki/Includes_(Effects)
// works quite well but what's the theory behind this?
{ return frac(sin(dot(p, float2(25.9796, 156.466))) * 43758.5453);
}

/*
float random2(in float2 p)
// my own ad hoc non-linear RNG using almost completely arbitrary numbers
// appears to produce acceptable textures
{ float x = dot(p, float2(57.3791, 493.15937)+13.50987);
  return frac(x * x * 13.50987);
}
*/

float random(in float2 p)
// returns a pseudo random float in the range 0 to 1
// done this way so only one line needs to be changed if another RNG is used
{ return random1(frac(p + seed));
  //return random2(frac(p + seed)); // used for testing logic - but random1 may give better results
}

VSOutput VShader(VSInput In, VSOutput Out)
{ float4 pos = In.pos;
  pos.xy = pos.xy + float2( -invSourceTextureWidth.x, invSourceTextureWidth.y );
  Out.pos = pos;
  Out.UV = In.UV;
  return Out;
};

PSOutput PShaderBasic(PSInput In, PSOutput Out)
{ octaveList[0] = list1.x;    // will this fudge work? [Yes, it does - let's hope the shader compiler extracts this as a pre-shader.]
  octaveList[1] = list1.y;
  octaveList[2] = list1.z;
  octaveList[3] = list1.w;
  octaveList[4] = list2.x;
  octaveList[5] = list2.y;
  octaveList[6] = list2.z;
  octaveList[7] = list2.w;
  float octaveWeight = 1.0;
  float invTileWidth = 0.5;
  float tileWidth = 2.0;
  float result = 0.0; // Perlin noise should range between -1 and +1 so for colour need to centre and rescale at 0.5 at end
  float upperBound = 0.0;
  float2 coeffs00;
  float2 coeffs01;
  float2 coeffs10;
  float2 coeffs11;
  // several octaves
  for ( int i = 0; i < maxOctaves; i++ )
  { octaveWeight *= octaveDivisor;
    invTileWidth *= 2.0;
    tileWidth *= 0.5;
    float tolerance = tileWidth * 0.5;
    float tolerance2 = 1.0 - tileWidth - tolerance;
    if (octaveList[i] == 1)
    { // find relative position within current tile
      float2 imageUV = frac(invTileWidth * In.UV);
      // recalculate the weights
      float2 weights = imageUV * imageUV * imageUV * (6.0 * imageUV * imageUV - 15.0 * imageUV + 10.0); // revised Perlin weights
      // find random tangents for new corners
      float2 offset  = floor(invTileWidth * In.UV) * tileWidth;

      // default values - change where necessary
      float2 tileUV00 = corner00*tileWidth+offset;
      float2 tileUV01 = corner01*tileWidth+offset;
      float2 tileUV10 = corner10*tileWidth+offset;
      float2 tileUV11 = corner11*tileWidth+offset;

      // finally we can compute the coefficients for the four corner tangents
      coeffs00 = float2 (random(tileUV00), random(tileUV00+seed2))-0.5.xx; // top left
      coeffs01 = float2 (random(tileUV01), random(tileUV01+seed2))-0.5.xx; // lower left
      coeffs10 = float2 (random(tileUV10), random(tileUV10+seed2))-0.5.xx; // top right
      coeffs11 = float2 (random(tileUV11), random(tileUV11+seed2))-0.5.xx; // lower right

      // find tangents at corners
      float tan00 = dot(coeffs00, imageUV-corner00);
      float tan01 = dot(coeffs01, imageUV-corner01);
      float tan10 = dot(coeffs10, imageUV-corner10);
      float tan11 = dot(coeffs11, imageUV-corner11);

      // interpolate tangents for current intermediate point
      float tA = lerp(tan00, tan10, weights.x);
      float tB = lerp(tan01, tan11, weights.x);
      result += lerp(tA, tB, weights.y) * octaveWeight; // need to check sensible values for this - e.g. what is max possible value unscaled?
      upperBound += octaveWeight * octaveDivisor;
    }
  }
  // rescale noise component to range +-0.5 (heuristic rule) to avoid whiteout
  result = 0.5 + boundFactor * result/upperBound;
  Out.colour = float4 (result, result, result, 1.0);
  return Out;
};

PSOutput PShaderSeamless(PSInput In, PSOutput Out)
{ octaveList[0] = list1.x;    // will this fudge work? [Yes, it does - let's hope the shader compiler extracts this as a pre-shader.]
  octaveList[1] = list1.y;
  octaveList[2] = list1.z;
  octaveList[3] = list1.w;
  octaveList[4] = list2.x;
  octaveList[5] = list2.y;
  octaveList[6] = list2.z;
  octaveList[7] = list2.w;
  float octaveWeight = 1.0;
  float invTileWidth = 0.5;
  float tileWidth = 2.0;
  float result = 0.0; // Perlin noise should range between -1 and +1 so for colour need to centre and rescale at 0.5 at end
  float upperBound = 0.0;
  float2 coeffs00;
  float2 coeffs01;
  float2 coeffs10;
  float2 coeffs11;
  // several octaves
  for ( int i = 0; i < maxOctaves; i++ )
  { octaveWeight *= octaveDivisor;
    invTileWidth *= 2.0;
    tileWidth *= 0.5;
    float tolerance = tileWidth * 0.5;
    float tolerance2 = 1.0 - tileWidth - tolerance;
    if (octaveList[i] == 1)
    { // find relative position within current tile
      float2 imageUV = frac(invTileWidth * In.UV);
      // recalculate the weights
      float2 weights = imageUV * imageUV * imageUV * (6.0 * imageUV * imageUV - 15.0 * imageUV + 10.0); // revised Perlin weights
      // find random tangents for new corners
      float2 offset  = floor(invTileWidth * In.UV) * tileWidth;

      // default values - change where necessary
      float2 tileUV00 = corner00*tileWidth+offset;
      float2 tileUV01 = corner01*tileWidth+offset;
      float2 tileUV10 = corner10*tileWidth+offset;
      float2 tileUV11 = corner11*tileWidth+offset;

      // test for single octave first as it is special ( :) )
      if (i == 0)
      { // all four corners must be the same for seamless output
        tileUV00 = 0.0.xx;
        tileUV01 = 0.0.xx;
        tileUV10 = 0.0.xx;
        tileUV11 = 0.0.xx;
      }
      else
      if ((offset.x < tolerance) && (offset.y < tolerance)) // i.e. top left corner tile
      { // explicitly define common values just in case
        tileUV00 = 0.0.xx; // define explicitly just in case
        tileUV10 = float2 (tileWidth+offset.x, 0.0);
        tileUV01 = float2 (0.0, tileWidth+offset.y);
      }
      else
      if ((offset.x < tolerance) && (offset.y > tolerance2)) // must be lower left corner tile
      { // lower edge coeffs equal extreme top edge coefficients
        tileUV00 = float2 (0.0, offset.y);
        tileUV01 = 0.0.xx;
        tileUV11 = float2 (tileWidth+offset.x, 0.0); // lower right
      }
      else
      if ((offset.x > tolerance2) && (offset.y < tolerance)) // must be upper right
      { // top edge coefficients equal extreme lower edge coefficients
        tileUV00 = float2 (offset.x, 0.0); // top left
        tileUV10 = 0.0.xx;
        tileUV11 = float2 (0.0, tileWidth+offset.y);
      }
      else
      if ((offset.x > tolerance2) && (offset.y > tolerance2)) // must be lower right
      { tileUV01 = float2 (offset.x, 0.0);
        tileUV10 = float2 (0.0, offset.y);
        tileUV11 = 0.0.xx;
      }
      // now include the extra tests for edge tiles between corners
      // each of these will require two coefficients to be reset
      else
      if ((offset.x < tolerance ) && (offset.y < tolerance2))
      { // must be left hand edge tile - so two corners need to be fixed explicitly
        tileUV00 = float2 (0.0, offset.y);
        tileUV01 = float2 (0.0, tileWidth+offset.y);
      }
      else
      if ((offset.x < tolerance2 ) && (offset.y < tolerance))
      { // must be top edge tile - so two corners need to be fixed explicitly
        tileUV00 = float2 (offset.x, 0.0);
        tileUV10 = float2 (tileWidth+offset.x, 0.0);
      }
      else
      if ((offset.x < tolerance2 ) && (offset.y > tolerance2))
      { // must be bottom edge tile - so two corners need to be fixed explicitly
        tileUV01 = float2 (offset.x, 0.0);
        tileUV11 = float2 (tileWidth+offset.x, 0.0);
      }
      else
      if ((offset.x > tolerance2 ) && (offset.y < tolerance2))
      { // must be right hand edge tile - so two corners need to be fixed explicitly
        tileUV10 = float2 (0.0, offset.y);
        tileUV11 = float2 (0.0, tileWidth+offset.y);
      }

      // finally we can compute the coefficients for the four corner tangents
      coeffs00 = float2 (random(tileUV00), random(tileUV00+seed2))-0.5.xx; // top left
      coeffs01 = float2 (random(tileUV01), random(tileUV01+seed2))-0.5.xx; // lower left
      coeffs10 = float2 (random(tileUV10), random(tileUV10+seed2))-0.5.xx; // top right
      coeffs11 = float2 (random(tileUV11), random(tileUV11+seed2))-0.5.xx; // lower right

      // find tangents at corners
      float tan00 = dot(coeffs00, imageUV-corner00);
      float tan01 = dot(coeffs01, imageUV-corner01);
      float tan10 = dot(coeffs10, imageUV-corner10);
      float tan11 = dot(coeffs11, imageUV-corner11);

      // interpolate tangents for current intermediate point
      float tA = lerp(tan00, tan10, weights.x);
      float tB = lerp(tan01, tan11, weights.x);
      result += lerp(tA, tB, weights.y) * octaveWeight; // need to check sensible values for this - e.g. what is max possible value unscaled?
      upperBound += octaveWeight * octaveDivisor;
    }
  }
  // rescale noise component to range +-0.5 (heuristic rule) to avoid whiteout
  result = 0.5 + boundFactor * result/upperBound;
  Out.colour = float4 (result, result, result, 1.0);
  return Out;
};

technique perlinNoiseBasic
{ pass p0
  { VertexShader = compile vs_3_0 VShader();
    PixelShader = compile ps_3_0 PShaderBasic();
  }
}

technique perlinNoiseSeamless
{ pass p0
  { VertexShader = compile vs_3_0 VShader();
    PixelShader = compile ps_3_0 PShaderSeamless();
  }
}
