precision mediump float;
#include <particle-common>
#define MAX_KEY_NUM 8
#define CURVE_MODE_CONSTANT 0
#define CURVE_MODE_RANDOM_CONSTANT 1
#define CURVE_MODE_CURVE 2
#define CURVE_MODE_RANDOM_CURVE 3

#define GRADIENT_MODE_FIX 0
#define GRADIENT_MODE_BLEND 1

#define GRADIENT_RANGE_MODE_COLOR 0
#define GRADIENT_RANGE_MODE_TWO_COLOR 1
#define GRADIENT_RANGE_MODE_RANDOM_COLOR 2
#define GRADIENT_RANGE_MODE_GRADIENT 3
#define GRADIENT_RANGE_MODE_TWO_GRADIENT 4

#define SIMULATE_SPACE_LOCAL 0
#define SIMULATE_SPACE_WORLD 1

#define ANIMATION_MODE_WHOLE_SHEET 0
#define ANIMATION_MODE_SINGLE_ROW 1

#define COLOR_OVERTIME_RAND_OFFSET 91041.
#define FORCE_OVERTIME_RAND_OFFSET 212165.
#define ROTATION_OVERTIME_RAND_OFFSET 125292.
#define SIZE_OVERTIME_RAND_OFFSET 39825.
#define TEXTURE_ANIMATION_RAND_OFFSET 90794.
#define VELOCITY_OVERTIME_RAND_OFFSET 197866.

#define DECL_CURVE_STRUCT(name) \
  uniform CurveStruct_##name## { \
    int u_##name##_curveMode; \
    float u_##name##_minConstant; \
    float u_##name##_maxConstant; \
    float u_##name##_minKeyTime[MAX_KEY_NUM]; \
    vec4 u_##name##_minKeyCoef[MAX_KEY_NUM]; \
    vec4 u_##name##_maxKeyCoef[MAX_KEY_NUM]; \
    float u_##name##_maxKeyTime[MAX_KEY_NUM]; \
  };

#define DECL_CURVE_STRUCT_INT(name) \
  uniform CurveStructInt_##name## { \
    int u_##name##_curveMode; \
    float u_##name##_minConstant; \
    float u_##name##_maxConstant; \
    float u_##name##_minKeyTime[MAX_KEY_NUM]; \
    vec4 u_##name##_minKeyCoef[MAX_KEY_NUM]; \
    vec4 u_##name##_maxKeyCoef[MAX_KEY_NUM]; \
    float u_##name##_maxKeyTime[MAX_KEY_NUM]; \
    float u_##name##_minIntegral[MAX_KEY_NUM - 1]; \
    float u_##name##_maxIntegral[MAX_KEY_NUM - 1]; \
  };

#define DECL_GRADIENT_STRUCT(name) \
  uniform GradientStruct_##name## { \
    int u_##name##_rangeMode; \
    int u_##name##_minGradMode; \
    int u_##name##_maxGradMode; \
    vec4 u_##name##_minColor; \
    vec4 u_##name##_maxColor; \
    vec3 u_##name##_minColorKeyValue[MAX_KEY_NUM]; \
    float u_##name##_minColorKeyTime[MAX_KEY_NUM]; \
    float u_##name##_minAlphaKeyValue[MAX_KEY_NUM]; \
    float u_##name##_minAlphaKeyTime[MAX_KEY_NUM]; \
    vec3 u_##name##_maxColorKeyValue[MAX_KEY_NUM]; \
    float u_##name##_maxColorKeyTime[MAX_KEY_NUM]; \
    float u_##name##_maxAlphaKeyValue[MAX_KEY_NUM]; \
    float u_##name##_maxAlphaKeyTime[MAX_KEY_NUM]; \
  };

#define EVAL_CURVE_RANGE(name, t, rnd) \
  evaluateCurveRange(u_##name##_curveMode, u_##name##_minConstant, u_##name##_maxConstant, u_##name##_minKeyTime, u_##name##_minKeyCoef, u_##name##_maxKeyTime, u_##name##_maxKeyCoef, t, rnd)

#define EVAL_CURVE_INTEGRAL(name, t, ts, rnd) \
  evaluateCurveRangeIntegral(u_##name##_curveMode, u_##name##_minConstant, u_##name##_maxConstant, u_##name##_minKeyTime, u_##name##_minKeyCoef, u_##name##_minIntegral, u_##name##_maxKeyTime, u_##name##_maxKeyCoef, u_##name##_maxIntegral, t, ts, rnd)

#define EVAL_CURVE_INTEGRAL_TWICE(name, t, ts, rnd) \
  evaluateCurveRangeIntegralTwice(u_##name##_curveMode, u_##name##_minConstant, u_##name##_maxConstant, u_##name##_minKeyTime, u_##name##_minKeyCoef, u_##name##_minIntegral, u_##name##_maxKeyTime, u_##name##_maxKeyCoef, u_##name##_maxIntegral, t, ts, rnd)

#define EVAL_GRADIENT_RANGE(name, t, rnd) \
  evaluateGradientRange(u_##name##_rangeMode, u_##name##_minColor, u_##name##_maxColor, \
  u_##name##_minGradMode, u_##name##_minColorKeyValue, u_##name##_minColorKeyTime, u_##name##_minAlphaKeyValue, u_##name##_minAlphaKeyTime, \
  u_##name##_maxGradMode, u_##name##_maxColorKeyValue, u_##name##_maxColorKeyTime, u_##name##_maxAlphaKeyValue, u_##name##_maxAlphaKeyTime, t, rnd);

in vec4 a_position_starttime; // center position,particle start time
in vec4 a_vertIdx_size_angle;  // xy:vertex index,z:size,w:angle
in vec4 a_color;
in vec4 a_dir_life;  // xyz:particle start velocity,w:particle lifetime
in float a_rndSeed;

uniform Constants2 {
  vec4 u_worldRot;
  float u_psTime;
  int u_velocity_space;
  float u_speedModifier;
  int u_force_space;
};

#if VELOCITY_OVERTIME_MODULE_ENABLE
// DECL_CURVE_STRUCT_INT(velocity_pos_x)
// DECL_CURVE_STRUCT_INT(velocity_pos_y)
// DECL_CURVE_STRUCT_INT(velocity_pos_z)
  #if USE_STRETCHED_BILLBOARD
    // DECL_CURVE_STRUCT(velocity_x)
    // DECL_CURVE_STRUCT(velocity_y)
    // DECL_CURVE_STRUCT(velocity_z)
  #endif
#endif

#if FORCE_OVERTIME_MODULE_ENABLE
// DECL_CURVE_STRUCT_INT(force_pos_x)
// DECL_CURVE_STRUCT_INT(force_pos_y)
// DECL_CURVE_STRUCT_INT(force_pos_z)
  #if USE_STRETCHED_BILLBOARD
    // DECL_CURVE_STRUCT_INT(force_vel_x)
    // DECL_CURVE_STRUCT_INT(force_vel_y)
    // DECL_CURVE_STRUCT_INT(force_vel_z)
  #endif
#endif

#if SIZE_OVERTIME_MODULE_ENABLE
// DECL_CURVE_STRUCT(size)
#endif

#if COLOR_OVERTIME_MODULE_ENABLE
// DECL_GRADIENT_STRUCT(color)
#endif

#if TEXTURE_ANIMATION_ENABLE
// DECL_CURVE_STRUCT(frameOverTime)
uniform Animation {
  float u_cycles;
  int u_animation_mode;
  bool u_random_row;
  int u_row_index;
};
#endif

#if ROTATE_OVERTIME_MODULE_ENABLE
// DECL_CURVE_STRUCT_INT(rotate)
#endif

float repeat(float t, float length) {
  return t - floor(t / length) * length;
}

vec4 rotateQuat(vec4 p, vec4 q) {
  vec3 iv = cross(q.xyz, p.xyz) + q.w * p.xyz;
  vec3 res = p.xyz + 2.0 * cross(q.xyz, iv);
  return vec4(res.xyz, p.w);
}

float random(float seed) {
  seed = mod(seed, 233280.);
  float q = (seed * 9301. + 49297.) / 233280.;
  return fract(q);
}

float calcCurveValue(vec4 coef, float t) {
  return t * (t * (t * coef.x + coef.y) + coef.z) + coef.w;
}

float evaluateCurve(float keyTime[MAX_KEY_NUM], vec4 keyCoef[MAX_KEY_NUM], float normalizedTime) {
  for (int i = 0; i < MAX_KEY_NUM; i++) {
    if (keyTime[i] > normalizedTime) {
      return calcCurveValue(keyCoef[i], normalizedTime - (i == 0 ? 0. : keyTime[i - 1]));
    }
  }
}

float evaluateIntegral(float keyTime[MAX_KEY_NUM], vec4 keyCoef[MAX_KEY_NUM], float integral[MAX_KEY_NUM - 1], float normalizedTime, float ts) {
  for (int i = 0; i < MAX_KEY_NUM; i++) {
    if (keyTime[i] > normalizedTime) {
      float t = normalizedTime - (i == 0 ? 0. : keyTime[i - 1]);
      return ts * ((i - 1 < 0 ? 0. : integral[i - 1]) + t * calcCurveValue(keyCoef[i], t));
    }
  }
}

float evaluateIntegralTwice(float keyTime[MAX_KEY_NUM], vec4 keyCoef[MAX_KEY_NUM], float integral[MAX_KEY_NUM - 1], float normalizedTime, float ts) {
  for (int i = 0; i < MAX_KEY_NUM; i++) {
    if (keyTime[i] > normalizedTime) {
      float t = normalizedTime - (i == 0 ? 0. : keyTime[i - 1]);
      return ts * ts * ((i - 1 < 0 ? 0. : integral[i - 1]) + t * t * calcCurveValue(keyCoef[i], t));
    }
  }
}

float evaluateCurveRange(int mode, float minConstant, float maxConstant
  , float minKeyTime[MAX_KEY_NUM], vec4 minKeyCoef[MAX_KEY_NUM]
  , float maxKeyTime[MAX_KEY_NUM], vec4 maxKeyCoef[MAX_KEY_NUM]
  , float t, float rnd) {
  if (mode == CURVE_MODE_CONSTANT) {
    return minConstant;
  } else if (mode == CURVE_MODE_RANDOM_CONSTANT) {
    return mix(minConstant, maxConstant, random(rnd));
  } else if (mode == CURVE_MODE_CURVE) {
    return evaluateCurve(minKeyTime, minKeyCoef, t);
  } else if (mode == CURVE_MODE_RANDOM_CURVE) {
    return mix(evaluateCurve(minKeyTime, minKeyCoef, t), evaluateCurve(maxKeyTime, maxKeyCoef, t), random(rnd));
  }
}

float evaluateCurveRangeIntegral(int mode, float minConstant, float maxConstant
  , float minKeyTime[MAX_KEY_NUM], vec4 minKeyCoef[MAX_KEY_NUM], float minIntegral[MAX_KEY_NUM - 1]
  , float maxKeyTime[MAX_KEY_NUM], vec4 maxKeyCoef[MAX_KEY_NUM], float maxIntegral[MAX_KEY_NUM - 1]
  , float t, float ts, float rnd) {
  if (mode == CURVE_MODE_CONSTANT) {
    return minConstant * t * ts;
  } else if (mode == CURVE_MODE_RANDOM_CONSTANT) {
    return mix(minConstant, maxConstant, random(rnd)) * t * ts;
  } else if (mode == CURVE_MODE_CURVE) {
    return evaluateIntegral(minKeyTime, minKeyCoef, minIntegral, t, ts);
  } else if (mode == CURVE_MODE_RANDOM_CURVE) {
    return mix(evaluateIntegral(minKeyTime, minKeyCoef, minIntegral, t, ts), evaluateIntegral(maxKeyTime, maxKeyCoef, maxIntegral, t, ts), random(rnd));
  }
}

float evaluateCurveRangeIntegralTwice(int mode, float minConstant, float maxConstant
  , float minKeyTime[MAX_KEY_NUM], vec4 minKeyCoef[MAX_KEY_NUM], float minIntegral[MAX_KEY_NUM - 1]
  , float maxKeyTime[MAX_KEY_NUM], vec4 maxKeyCoef[MAX_KEY_NUM], float maxIntegral[MAX_KEY_NUM - 1]
  , float t, float ts, float rnd) {
  if (mode == CURVE_MODE_CONSTANT) {
    return minConstant * t * t * ts * ts / 2.;
  } else if (mode == CURVE_MODE_RANDOM_CONSTANT) {
    return mix(minConstant, maxConstant, random(rnd)) * t * t * ts * ts / 2.;
  } else if (mode == CURVE_MODE_CURVE) {
    return evaluateIntegralTwice(minKeyTime, minKeyCoef, minIntegral, t, ts);
  } else if (mode == CURVE_MODE_RANDOM_CURVE) {
    return mix(evaluateIntegralTwice(minKeyTime, minKeyCoef, minIntegral, t, ts), evaluateIntegralTwice(maxKeyTime, maxKeyCoef, maxIntegral, t, ts), random(rnd));
  }
}

vec4 evaluateGradient(int mode, float colorKeyTime[MAX_KEY_NUM], vec3 colorKeyValue[MAX_KEY_NUM]
  , float alphaKeyTime[MAX_KEY_NUM], float alphaKeyValue[MAX_KEY_NUM]
  , float t){
  vec4 ret;
  for (int i = 0; i < MAX_KEY_NUM; i++) {
    if (t < colorKeyTime[i]) {
      if (mode == GRADIENT_MODE_FIX) {
        ret.xyz = colorKeyValue[i];
      } else if (mode == GRADIENT_MODE_BLEND) {
        ret.xyz = mix(colorKeyValue[i - 1], colorKeyValue[i], (t - colorKeyTime[i - 1]) / (colorKeyTime[i] - colorKeyTime[i - 1]));
      }
      break;
    }
  }
  for (int i = 0; i < MAX_KEY_NUM; i++) {
    if (t < alphaKeyTime[i]) {
      if (mode == GRADIENT_MODE_FIX) {
        ret.w = alphaKeyValue[i];
      } else if (mode == GRADIENT_MODE_BLEND) {
        ret.w = mix(alphaKeyValue[i - 1], alphaKeyValue[i], (t - alphaKeyTime[i - 1]) / (alphaKeyTime[i] - alphaKeyTime[i - 1]));
      }
      break;
    }
  }
  return ret;
}

vec4 evaluateGradientRange(int rangeMode, vec4 minColor, vec4 maxColor,
  int minGradMode, vec3 minColorKeyValue[MAX_KEY_NUM], float minColorKeyTime[MAX_KEY_NUM], float minAlphaKeyValue[MAX_KEY_NUM], float minAlphaKeyTime[MAX_KEY_NUM],
  int maxGradMode, vec3 maxColorKeyValue[MAX_KEY_NUM], float maxColorKeyTime[MAX_KEY_NUM], float maxAlphaKeyValue[MAX_KEY_NUM], float maxAlphaKeyTime[MAX_KEY_NUM],
  float t, float rnd){
  if (rangeMode == GRADIENT_RANGE_MODE_COLOR) {
    return minColor;
  } else if (rangeMode == GRADIENT_RANGE_MODE_TWO_COLOR) {
    return mix(minColor, maxColor, rnd);
  } else if (rangeMode == GRADIENT_RANGE_MODE_GRADIENT) {
    return evaluateGradient(minGradMode, minColorKeyTime, minColorKeyValue, minAlphaKeyTime, minAlphaKeyValue, t);
  } else if (rangeMode == GRADIENT_RANGE_MODE_TWO_GRADIENT) {
    return mix(evaluateGradient(minGradMode, minColorKeyTime, minColorKeyValue, minAlphaKeyTime, minAlphaKeyValue, t),
      evaluateGradient(maxGradMode, maxColorKeyTime, maxColorKeyValue, maxAlphaKeyTime, maxAlphaKeyValue, t), rnd);
  }
}

vec4 gpvs_main() {
  vec4 pos = vec4(a_position_starttime.xyz, 1.);
  float activeTime = u_psTime - a_position_starttime.w;
  float normalizedTime = activeTime / a_dir_life.w;

  #if VELOCITY_OVERTIME_MODULE_ENABLE
    float speedModifier = u_speedModifier;
  #else
    float speedModifier = 1.;
  #endif
  pos.xyz += a_dir_life.xyz * activeTime * speedModifier;

  #if USE_STRETCHED_BILLBOARD
    vec4 velocity = vec4(a_dir_life.xyz, 0.);
    velocity *= speedModifier;
  #endif
  #if !USE_WORLD_SPACE
    pos = cc_matWorld * pos;
    #if USE_STRETCHED_BILLBOARD
      velocity = rotateQuat(velocity, u_worldRot);
    #endif
  #endif
  #if VELOCITY_OVERTIME_MODULE_ENABLE
    vec4 velocityTrack = vec4(EVAL_CURVE_INTEGRAL(velocity_pos_x, normalizedTime, a_dir_life.w, a_rndSeed + VELOCITY_OVERTIME_RAND_OFFSET), EVAL_CURVE_INTEGRAL(velocity_pos_y, normalizedTime, a_dir_life.w, a_rndSeed + VELOCITY_OVERTIME_RAND_OFFSET), EVAL_CURVE_INTEGRAL(velocity_pos_z, normalizedTime, a_dir_life.w, a_rndSeed + VELOCITY_OVERTIME_RAND_OFFSET), 0);
    velocityTrack = velocityTrack * speedModifier;
    if (u_velocity_space == SIMULATE_SPACE_LOCAL) {
      velocityTrack = rotateQuat(velocityTrack, u_worldRot);
    }
    pos += velocityTrack;
    #if USE_STRETCHED_BILLBOARD
      vec4 velocityVel = vec4(EVAL_CURVE_RANGE(velocity_x, normalizedTime, a_dir_life.w, a_rndSeed + VELOCITY_OVERTIME_RAND_OFFSET), EVAL_CURVE_RANGE(velocity_y, normalizedTime, a_dir_life.w, a_rndSeed + VELOCITY_OVERTIME_RAND_OFFSET), EVAL_CURVE_RANGE(velocity_z, normalizedTime, a_dir_life.w, a_rndSeed + VELOCITY_OVERTIME_RAND_OFFSET), 0);
      if (u_velocity_space == SIMULATE_SPACE_LOCAL) {
        velocityVel = rotateQuat(velocityVel, u_worldRot);
      }
      velocityVel *= speedModifier;
      velocity += velocityVel;
    #endif
  #endif

  #if FORCE_OVERTIME_MODULE_ENABLE
    vec4 forceTrack = vec4(EVAL_CURVE_INTEGRAL_TWICE(force_pos_x, normalizedTime, a_dir_life.w, a_rndSeed + FORCE_OVERTIME_RAND_OFFSET), EVAL_CURVE_INTEGRAL_TWICE(force_pos_y, normalizedTime, a_dir_life.w, a_rndSeed + FORCE_OVERTIME_RAND_OFFSET), EVAL_CURVE_INTEGRAL_TWICE(force_pos_z, normalizedTime, a_dir_life.w, a_rndSeed + FORCE_OVERTIME_RAND_OFFSET), 0);
    forceTrack = forceTrack * speedModifier;
    if (u_force_space == SIMULATE_SPACE_LOCAL) {
      forceTrack = rotateQuat(forceTrack, u_worldRot);
    }
    pos += forceTrack;
    #if USE_STRETCHED_BILLBOARD
      vec4 forceVel = vec4(EVAL_CURVE_INTEGRAL(force_vel_x, normalizedTime, a_dir_life.w, a_rndSeed + FORCE_OVERTIME_RAND_OFFSET), EVAL_CURVE_INTEGRAL(force_vel_y, normalizedTime, a_dir_life.w, a_rndSeed + FORCE_OVERTIME_RAND_OFFSET), EVAL_CURVE_INTEGRAL(force_vel_z, normalizedTime, a_dir_life.w, a_rndSeed + FORCE_OVERTIME_RAND_OFFSET), 0);
      if (u_force_space == SIMULATE_SPACE_LOCAL) {
        forceVel = rotateQuat(forceVel, u_worldRot);
      }
      forceVel *= speedModifier;
      velocity += forceVel;
    #endif
  #endif

  float size = a_vertIdx_size_angle.z;
  #if SIZE_OVERTIME_MODULE_ENABLE
    float sizeModifier = EVAL_CURVE_RANGE(size, normalizedTime, a_rndSeed + SIZE_OVERTIME_RAND_OFFSET);
    size *= sizeModifier;
  #endif

  vec2 cornerOffset = vec2((a_vertIdx_size_angle.xy - 0.5) * size);
  #if !USE_STRETCHED_BILLBOARD
    float angle = a_vertIdx_size_angle.w;
    #if ROTATE_OVERTIME_MODULE_ENABLE
      angle += EVAL_CURVE_INTEGRAL(rotate, normalizedTime, a_dir_life.w, a_rndSeed + ROTATION_OVERTIME_RAND_OFFSET);
    #endif
    rotateCorner(cornerOffset, angle);
  #endif

  computeVertPos(pos, cornerOffset
  #if USE_BILLBOARD || USE_VERTICAL_BILLBOARD
    , cc_matView
  #endif
  #if USE_STRETCHED_BILLBOARD
    , cc_cameraPos
    , velocity
    , velocityScale
    , lengthScale
    , size
    , a_vertIdx_size_angle.x
  #endif
  );

  pos = cc_matViewProj * pos;

  float frameIndex = 0.;
  #if TEXTURE_ANIMATION_ENABLE
    if (u_animation_mode == ANIMATION_MODE_WHOLE_SHEET) {
      frameIndex = repeat(u_cycles * EVAL_CURVE_RANGE(frameOverTime, normalizedTime, a_rndSeed + TEXTURE_ANIMATION_RAND_OFFSET), 1.);
    } else if (u_animation_mode == ANIMATION_MODE_SINGLE_ROW) {
      float rowLength = 1. / frameTile_velLenScale.y;
      if (u_random_row) {
        float f = repeat(u_cycles * EVAL_CURVE_RANGE(frameOverTime, normalizedTime, a_rndSeed + TEXTURE_ANIMATION_RAND_OFFSET), 1.);
        float startRow = floor(random(floor(u_psTime * 1000.)) * frameTile_velLenScale.y);
        float from = startRow * rowLength;
        float to = from + rowLength;
        frameIndex = mix(from, to, f);
      }
      else {
        float from = float(u_row_index) * rowLength;
        float to = from + rowLength;
        frameIndex = mix(from, to, repeat(u_cycles * EVAL_CURVE_RANGE(frameOverTime, normalizedTime, a_rndSeed + TEXTURE_ANIMATION_RAND_OFFSET), 1.));
      }
    }
  #endif
  uv = computeUV(frameIndex, a_vertIdx_size_angle.xy, frameTile_velLenScale.xy) * mainTiling_Offset.xy + mainOffset.zw;

  #if COLOR_OVERTIME_MODULE_ENABLE
    color = a_color * EVAL_GRADIENT_RANGE(color, normalizedTime, a_rndSeed + COLOR_OVERTIME_RAND_OFFSET);
  #else
    color = a_color;
  #endif

  return pos;
}