vec3 applyMatrix( vec3 v, mat4 m )
{
  return ( m * vec4( v, 1.0 ) ).xyz;
}

vec3 applyMatrixWithTranslation( vec3 v, mat4 m )
{
  mat4 mw = m;
  mw[ 3 ][ 0 ] = 0.0;
  mw[ 3 ][ 1 ] = 0.0;
  mw[ 3 ][ 2 ] = 0.0;
  mw[ 3 ][ 3 ] = 1.0;

  return ( mw * vec4( v, 1.0 ) ).xyz;
}

//   L W V
// L
// W
// V

//    LD WD VD
// LD
// WD
// VD

vec3 localToWorld( vec3 _VERTEX, mat4 _MODEL_MATRIX )
{
  return ( _MODEL_MATRIX * vec4( _VERTEX, 1.0 ) ).xyz;
}

vec3 localToWorldDirection( vec3 _VERTEX, mat4 _MODEL_MATRIX )
{
  mat4 mw = _MODEL_MATRIX;
  mw[ 3 ][ 0 ] = 0.0;
  mw[ 3 ][ 1 ] = 0.0;
  mw[ 3 ][ 2 ] = 0.0;
  mw[ 3 ][ 3 ] = 1.0;

  return ( mw * vec4( _VERTEX, 1.0 ) ).xyz;
}

vec3 localToView( vec3 _VERTEX, mat4 _MODELVIEW_MATRIX )
{
  return ( _MODELVIEW_MATRIX * vec4( _VERTEX, 1.0 ) ).xyz;
}

vec3 worldToLocal( vec3 _VERTEX, mat4 _MODEL_MATRIX )
{
  return ( inverse( _MODEL_MATRIX ) * vec4( _VERTEX, 1.0 ) ).xyz;
}

vec3 worldToLocalDirection( vec3 _VERTEX, mat4 _MODEL_MATRIX )
{
  mat4 mw = inverse( _MODEL_MATRIX );
  mw[ 3 ][ 0 ] = 0.0;
  mw[ 3 ][ 1 ] = 0.0;
  mw[ 3 ][ 2 ] = 0.0;
  mw[ 3 ][ 3 ] = 1.0;

  return ( mw * vec4( _VERTEX, 1.0 ) ).xyz;
}


vec3 worldToView( vec3 world, mat4 _VIEW_MATRIX )
{
  return ( _VIEW_MATRIX * vec4( world, 1.0 ) ).xyz;
}

vec3 worldToViewDirection( vec3 direction, mat4 _VIEW_MATRIX )
{
  mat4 mw = _VIEW_MATRIX;
  mw[ 3 ][ 0 ] = 0.0;
  mw[ 3 ][ 1 ] = 0.0;
  mw[ 3 ][ 2 ] = 0.0;
  mw[ 3 ][ 3 ] = 1.0;

  return ( mw * vec4( direction, 1.0 ) ).xyz;
}

vec3 viewToWorld( vec3 view, mat4 _INV_VIEW_MATRIX )
{
  return ( _INV_VIEW_MATRIX * vec4( view, 1.0 ) ).xyz;
}

vec3 viewToWorldDirection( vec3 view, mat4 _INV_VIEW_MATRIX )
{
  mat4 mw = _INV_VIEW_MATRIX;
  mw[ 3 ][ 0 ] = 0.0;
  mw[ 3 ][ 1 ] = 0.0;
  mw[ 3 ][ 2 ] = 0.0;
  mw[ 3 ][ 3 ] = 1.0;


  return ( mw * vec4( view, 1.0 ) ).xyz;
}

vec3 viewToLocal( vec3 view, mat4 _INV_VIEW_MATRIX, mat4 _MODEL_MATRIX )
{
  vec3 world = viewToWorld( view, _INV_VIEW_MATRIX );
  return worldToLocal( world, _MODEL_MATRIX );
}

vec3 viewToLocalDirection( vec3 view, mat4 _INV_VIEW_MATRIX, mat4 _MODEL_MATRIX )
{
  vec3 world = viewToWorldDirection( view, _INV_VIEW_MATRIX );
  return worldToLocalDirection( world, _MODEL_MATRIX );
}

vec3 extractScale( mat3 _MODEL_NORMAL_MATRIX )
{
  mat3 m = _MODEL_NORMAL_MATRIX;
  
  float x = length( vec3( m[ 0 ][ 0 ], m[ 1 ][ 0 ], m[ 2 ][ 0 ] ) ); 
  float y = length( vec3( m[ 0 ][ 1 ], m[ 1 ][ 1 ], m[ 2 ][ 1 ] ) ); 
  float z = length( vec3( m[ 0 ][ 2 ], m[ 1 ][ 2 ], m[ 2 ][ 2 ] ) ); 

  return vec3( x, y, z );
}

vec2 tilingOffset( vec2 uv, vec4 tilingOffset )
{
  uv *= tilingOffset.xy;
  uv += tilingOffset.zw;

  return uv;
}

vec2 tilingOffsetRepeat( vec2 uv, vec4 tilingOffset )
{
  uv *= tilingOffset.xy;
  uv += tilingOffset.zw;

  return mod( uv, vec2(1,1) );
}

vec3 billboardWorldOffset( vec2 _UV, mat4 _INV_VIEW_MATRIX, mat4 _MODEL_MATRIX  )
{
  vec2 mappedUV = mix( vec2(-1,1), vec2( 1, -1 ), _UV );
  vec4 offset = vec4( mappedUV.x, mappedUV.y, 0, 0 );

  offset = _INV_VIEW_MATRIX * offset;
  mat4 mw = _MODEL_MATRIX;
  mw[ 3 ][ 0 ] = 0.0;
  mw[ 3 ][ 1 ] = 0.0;
  mw[ 3 ][ 2 ] = 0.0;

  vec3 worldOffset = worldToLocal( offset.xyz, mw );
  worldOffset = normalize( worldOffset );

  return worldOffset;
}

vec2 rotate_v2( vec2 uv, float angle )
{
  float s = sin( angle );
  float c = cos( angle );

    
  float x = uv.x;
  float y = uv.y; 
 
  uv.x = c * x - s * y;
  uv.y = s * x + c * y;

  return uv;
}

vec2 rotateAround_v2( vec2 uv, float angle, vec2 pivot )
{
  uv -= pivot;
  uv =  rotate_v2( uv, angle );
  uv += pivot;

  return uv;
}

vec2 rotateAroundTexture_v2( vec2 uv, float angle, vec2 pivot, vec2 textureSize ) 
{

    vec2 p = uv;
    float aspect = textureSize.x / textureSize.y;
    
    float cosA = cos( angle );
    float sinA = sin( angle );
    
    mat2 rotMat = mat2( 
      vec2( cosA, -sinA ), 
      vec2( sinA, cosA )
     );

    rotMat = mat2( 
      vec2( cosA, -sinA ), 
      vec2( sinA, cosA )
     );


    p -= pivot;
    p.y *= 1.0 / aspect;
    p *= rotMat;
    
    p.y *= aspect;
    p += pivot;

    return p;

  }

vec3 cameraWorldPosition( mat4 _INV_VIEW_MATRIX )
{
  return (_INV_VIEW_MATRIX * vec4(vec3(0.0), 1.0)).xyz;
}

vec3 cameraWorldForward( mat4 _INV_VIEW_MATRIX )
{
  vec3 pos = cameraWorldPosition( _INV_VIEW_MATRIX );
  return normalize( (_INV_VIEW_MATRIX * vec4( vec3(0.0,0.0,1.0), 1.0)).xyz - pos );
}


mat3 identity_m3()
{
  return mat3(
    vec3( 1, 0, 0 ),
    vec3( 0, 1, 0 ),
    vec3( 0, 0, 1 )
  );
} 

mat3 translate_m3( vec2 translation )
{
  return mat3(
    vec3( 1, 0, translation.x ),
    vec3( 0, 1, translation.y ),
    vec3( 0, 0, 1 )
  );
}

mat3 scale_m3( vec2 scale )
{
  return mat3(
    vec3( scale.x, 0, 0 ),
    vec3( 0, scale.y, 0 ),
    vec3( 0, 0, 1 )
  );
}

mat3 scalePivot_m3( vec2 scale, vec2 pivot )
{
  return translate_m3( -pivot ) * scale_m3( scale ) * translate_m3( pivot );
}

mat3 rotate_m3( float radiansAngle )
{
  float c = cos( radiansAngle );
  float s = sin( radiansAngle );

  return mat3(
    vec3( c, -s, 0 ),
    vec3( s,  c, 0 ),
    vec3( 0,  0, 1 )
  );
}

mat3 rotatePivot_m3( float radiansAngle, vec2 pivot )
{
  return translate_m3( -pivot ) * rotate_m3( radiansAngle ) * translate_m3( pivot );
}

mat3 rotatePivotAspect_m3( float radiansAngle, vec2 pivot, float aspect )
{
  return translate_m3( -pivot ) * 
         scale_m3( vec2( 1, 1.0/aspect ) ) * 
         rotate_m3( radiansAngle ) * 
         scale_m3( vec2( 1, aspect ) ) *
         translate_m3( pivot );
}


vec2 applyMatrix_m3v2( mat3 matrix, vec2 point )
{
  float x = point.x;
  float y = point.y;

  // float ox = matrix[ 0 ] * x + matrix[ 3 ] * y + matrix[ 6 ];
	// float oy = matrix[ 1 ] * x + matrix[ 4 ] * y + matrix[ 7 ];
  
  // 0 1 2   0 3 6  
  // 3 4 5   1 4 7
  // 6 7 8   2 5 8

  vec3 mx = matrix[ 0 ];
  vec3 my = matrix[ 1 ];

  float ox = mx.x * x + mx.y * y + mx.z;
	float oy = my.x * x + my.y * y + my.z;

  return vec2( ox, oy );
}

vec2 applyMatrixBase_m3v2( mat3 matrix, vec2 point )
{
  float x = point.x;
  float y = point.y;

  vec3 mx = matrix[ 0 ];
  vec3 my = matrix[ 1 ];

  float ox = mx.x * x + mx.y * y;
	float oy = my.x * x + my.y * y;

  return vec2( ox, oy );
}


mat4 identity_m4()
{
  return mat4(
    vec4( 1, 0, 0, 0 ),
    vec4( 0, 1, 0, 0 ),
    vec4( 0, 0, 1, 0 ),
    vec4( 0, 0, 0, 1 )
  );
} 

mat4 translate_m4( vec3 translation )
{
  return mat4(
    vec4( 1, 0, 0, translation.x ),
    vec4( 0, 1, 0, translation.y ),
    vec4( 0, 0, 1, translation.z ),
    vec4( 0, 0, 0, 1 )
  );
}

mat4 scale_m4( vec3 scale )
{
  return mat4(
    vec4( scale.x, 0, 0, 0 ),
    vec4( 0, scale.y, 0, 0 ),
    vec4( 0, 0, scale.z, 0 ),
    vec4( 0, 0, 0, 1 )
  );
}


mat4 rotationX_m4( float radiansAngle )
{
  float c = cos( radiansAngle );
  float s = sin( radiansAngle );

  return mat4(
    vec4( 1, 0, 0, 0 ),
    vec4( 0, c, -s, 0 ),
    vec4( 0, s, c, 0 ),
    vec4( 0, 0, 0, 1 )
  );
}

mat4 rotationY_m4( float radiansAngle )
{
  float c = cos( radiansAngle );
  float s = sin( radiansAngle );

  return mat4(
    vec4( c, 0, s, 0 ),
    vec4( 0, 1, 0, 0 ),
    vec4( -s, 0, c, 0 ),
    vec4( 0, 0, 0, 1 )
  );
}

mat4 rotationZ_m4( float radiansAngle )
{
  float c = cos( radiansAngle );
  float s = sin( radiansAngle );

  return mat4(
    vec4( c, -s, 0, 0 ),
    vec4( s, c, 0, 0 ),
    vec4( 0, 0, 1, 0 ),
    vec4( 0, 0, 0, 1 )
  );
}