// ======================================================================== //
// Copyright 2009-2017 Intel Corporation                                    //
//                                                                          //
// Licensed under the Apache License, Version 2.0 (the "License");          //
// you may not use this file except in compliance with the License.         //
// You may obtain a copy of the License at                                  //
//                                                                          //
//     http://www.apache.org/licenses/LICENSE-2.0                           //
//                                                                          //
// Unless required by applicable law or agreed to in writing, software      //
// distributed under the License is distributed on an "AS IS" BASIS,        //
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. //
// See the License for the specific language governing permissions and      //
// limitations under the License.                                           //
// ======================================================================== //

#pragma once

#include "vec.isph"

struct LinearSpace3f 
{
  Vec3f vx;
  Vec3f vy;
  Vec3f vz;
};

struct LinearSpace3fa
{
  Vec3fa vx;
  Vec3fa vy;
  Vec3fa vz;
};

////////////////////////////////////////////////////////////////////////////////
/// Constructors
////////////////////////////////////////////////////////////////////////////////

inline uniform LinearSpace3f make_LinearSpace3f(const uniform Vec3f &x, const uniform Vec3f &y, const uniform Vec3f &z) { 
  uniform LinearSpace3f l; l.vx = x; l.vy = y; l.vz = z; return l; 
}

inline varying LinearSpace3f make_LinearSpace3f(const varying Vec3f &x, const varying Vec3f &y, const varying Vec3f &z) { 
  varying LinearSpace3f l; l.vx = x; l.vy = y; l.vz = z; return l; 
}

inline uniform LinearSpace3f make_LinearSpace3f(const uniform LinearSpace3fa &s) { 
  uniform LinearSpace3f l; l.vx = make_Vec3f(s.vx); l.vy = make_Vec3f(s.vy); l.vz = make_Vec3f(s.vz); return l; 
}

inline varying LinearSpace3f make_LinearSpace3f(const varying LinearSpace3fa &s) { 
  varying LinearSpace3f l; l.vx = make_Vec3f(s.vx); l.vy = make_Vec3f(s.vy); l.vz = make_Vec3f(s.vz); return l; 
}

inline LinearSpace3f make_LinearSpace3f_identity()
{
  return make_LinearSpace3f(make_Vec3f(1.f,0.f,0.f),
                            make_Vec3f(0.f,1.f,0.f),
                            make_Vec3f(0.f,0.f,1.f));
}

/*! return scale matrix */
inline uniform LinearSpace3f make_LinearSpace3f_scale(const uniform Vec3f& s) 
{
  return make_LinearSpace3f( make_Vec3f( s.x, 0.0f, 0.0f),
                             make_Vec3f(0.0f,  s.y, 0.0f),
                             make_Vec3f(0.0f, 0.0f,  s.z));
}

/*! return matrix for rotation around arbitrary axis */
inline uniform LinearSpace3f make_LinearSpace3f_rotate(const uniform Vec3f& _u, const uniform float& r) 
{
  uniform Vec3f u = normalize(_u);
  uniform float s = sin(r), c = cos(r);
  return make_LinearSpace3f(make_Vec3f(u.x*u.x+(1-u.x*u.x)*c, u.x*u.y*(1-c)+u.z*s,  u.x*u.z*(1-c)-u.y*s),
                            make_Vec3f(u.x*u.y*(1-c)-u.z*s, u.y*u.y+(1-u.y*u.y)*c, u.y*u.z*(1-c)+u.x*s),
                            make_Vec3f(u.x*u.z*(1-c)+u.y*s, u.y*u.z*(1-c)-u.x*s, u.z*u.z+(1-u.z*u.z)*c));
}
  
////////////////////////////////////////////////////////////////////////////////
// Unary Operators
////////////////////////////////////////////////////////////////////////////////

inline uniform LinearSpace3f neg(const uniform LinearSpace3f &l) { return make_LinearSpace3f(neg(l.vx),neg(l.vy),neg(l.vz)); }
inline varying LinearSpace3f neg(const varying LinearSpace3f &l) { return make_LinearSpace3f(neg(l.vx),neg(l.vy),neg(l.vz)); }

/*! compute the determinant of the matrix */
inline uniform float det(const uniform LinearSpace3f &l) { return dot(l.vx,cross(l.vy,l.vz)); }
inline varying float det(const varying LinearSpace3f &l) { return dot(l.vx,cross(l.vy,l.vz)); }

/*! compute transposed matrix */
inline uniform LinearSpace3f transposed(const uniform LinearSpace3f &l) { 
  return make_LinearSpace3f(make_Vec3f(l.vx.x,l.vy.x,l.vz.x),
                            make_Vec3f(l.vx.y,l.vy.y,l.vz.y),
                            make_Vec3f(l.vx.z,l.vy.z,l.vz.z));
}
inline varying LinearSpace3f transposed(const varying LinearSpace3f &l) { 
  return make_LinearSpace3f(make_Vec3f(l.vx.x,l.vy.x,l.vz.x),
                            make_Vec3f(l.vx.y,l.vy.y,l.vz.y),
                            make_Vec3f(l.vx.z,l.vy.z,l.vz.z));
}

/*! compute adjoint matrix */
inline uniform LinearSpace3f adjoint(const uniform LinearSpace3f &l) { 
  return transposed(make_LinearSpace3f(cross(l.vy,l.vz),cross(l.vz,l.vx),cross(l.vx,l.vy))); 
}
inline varying LinearSpace3f adjoint(const varying LinearSpace3f &l) { 
  return transposed(make_LinearSpace3f(cross(l.vy,l.vz),cross(l.vz,l.vx),cross(l.vx,l.vy))); 
}

/*! calculates orthogonal coordinate frame with z-Vector pointing towards N */
inline uniform LinearSpace3f frame(const uniform Vec3f &N) 
{
  const uniform Vec3f dx0 = make_Vec3f(0.0f,N.z,-N.y);
  const uniform Vec3f dx1 = make_Vec3f(-N.z,0.0f,N.x);
  const uniform Vec3f dx = normalize(dot(dx0,dx0) > dot(dx1,dx1) ? dx0 : dx1);
  const uniform Vec3f dy = normalize(cross(N,dx));
  return make_LinearSpace3f(dx,dy,N);
}

inline varying LinearSpace3f frame(const varying Vec3f &N) 
{
  const varying Vec3f dx0 = make_Vec3f(0.0f,N.z,-N.y);
  const varying Vec3f dx1 = make_Vec3f(-N.z,0.0f,N.x);
  const varying Vec3f dx = normalize(dot(dx0,dx0) > dot(dx1,dx1) ? dx0 : dx1);
  const varying Vec3f dy = normalize(cross(N,dx));
  return make_LinearSpace3f(dx,dy,N);
}

////////////////////////////////////////////////////////////////////////////////
/// Binary Operators
////////////////////////////////////////////////////////////////////////////////

inline uniform LinearSpace3f operator+(const uniform LinearSpace3f &a, const uniform LinearSpace3f &b) { return make_LinearSpace3f(a.vx+b.vx, a.vy+b.vy, a.vz+b.vz); }
inline varying LinearSpace3f operator+(const varying LinearSpace3f &a, const varying LinearSpace3f &b) { return make_LinearSpace3f(a.vx+b.vx, a.vy+b.vy, a.vz+b.vz); }

inline uniform LinearSpace3f operator-(const uniform LinearSpace3f &a, const uniform LinearSpace3f &b) { return make_LinearSpace3f(a.vx-b.vx, a.vy-b.vy, a.vz-b.vz); }
inline varying LinearSpace3f operator-(const varying LinearSpace3f &a, const varying LinearSpace3f &b) { return make_LinearSpace3f(a.vx-b.vx, a.vy-b.vy, a.vz-b.vz); }

inline uniform Vec3f         operator*(const uniform LinearSpace3f &l, const uniform Vec3f &v) { return v.x*l.vx + v.y*l.vy + v.z*l.vz; }
inline varying Vec3f         operator*(const uniform LinearSpace3f &l, const varying Vec3f &v) { return v.x*l.vx + v.y*l.vy + v.z*l.vz; }
inline varying Vec3f         operator*(const varying LinearSpace3f &l, const varying Vec3f &v) { return v.x*l.vx + v.y*l.vy + v.z*l.vz; }

inline uniform LinearSpace3f operator*(const uniform float         &a, const uniform LinearSpace3f &b) { return make_LinearSpace3f(a*b.vx, a*b.vy, a*b.vz); }
inline uniform LinearSpace3f operator*(const uniform LinearSpace3f &a, const uniform float         &b) { return make_LinearSpace3f(a.vx*b, a.vy*b, a.vz*b); }
inline uniform LinearSpace3f operator*(const uniform LinearSpace3f &a, const uniform LinearSpace3f &b) { return make_LinearSpace3f(a*b.vx, a*b.vy, a*b.vz); }

inline varying LinearSpace3f operator*(const varying float         &a, const varying LinearSpace3f &b) { return make_LinearSpace3f(a*b.vx, a*b.vy, a*b.vz); }
inline varying LinearSpace3f operator*(const varying LinearSpace3f &a, const varying float         &b) { return make_LinearSpace3f(a.vx*b, a.vy*b, a.vz*b); }
inline varying LinearSpace3f operator*(const varying LinearSpace3f &a, const varying LinearSpace3f &b) { return make_LinearSpace3f(a*b.vx, a*b.vy, a*b.vz); }

inline uniform Vec3f xfmVector(const uniform LinearSpace3f l, const uniform Vec3f v) { return v.x*l.vx + v.y*l.vy + v.z*l.vz; }
inline varying Vec3f xfmVector(const uniform LinearSpace3f l, const varying Vec3f v) { return v.x*l.vx + v.y*l.vy + v.z*l.vz; }
inline varying Vec3f xfmVector(const varying LinearSpace3f l, const varying Vec3f v) { return v.x*l.vx + v.y*l.vy + v.z*l.vz; }

////////////////////////////////////////////////////////////////////////////////
/// Comparison Operators
////////////////////////////////////////////////////////////////////////////////

inline uniform bool eq(const uniform LinearSpace3f &a, const uniform LinearSpace3f &b) { return eq(a.vx,b.vx) & eq(a.vy,b.vy) & eq(a.vz,b.vz); }
inline varying bool eq(const varying LinearSpace3f &a, const varying LinearSpace3f &b) { return eq(a.vx,b.vx) & eq(a.vy,b.vy) & eq(a.vz,b.vz); }

inline uniform bool ne(const uniform LinearSpace3f &a, const uniform LinearSpace3f &b) { return ne(a.vx,b.vx) | ne(a.vy,b.vy) | ne(a.vz,b.vz); }
inline varying bool ne(const varying LinearSpace3f &a, const varying LinearSpace3f &b) { return ne(a.vx,b.vx) | ne(a.vy,b.vy) | ne(a.vz,b.vz); }

////////////////////////////////////////////////////////////////////////////////
// Unary Operators
////////////////////////////////////////////////////////////////////////////////

/*! compute inverse matrix */
inline uniform LinearSpace3f rcp(const uniform LinearSpace3f &l) { return rcp(det(l))*adjoint(l); }
inline varying LinearSpace3f rcp(const varying LinearSpace3f &l) { return rcp(det(l))*adjoint(l); }
