/*	 Some 3D math routines
 *
 * Copyright 2001 TransGaming Technologies Inc.
 */

#include "config.h"

#include <assert.h>
#include <stdlib.h>

#include "wine/debug.h"
#include "d3d.h"

#include "d3d_private.h"
#include "d3ddevice/maths.h"

WINE_DEFAULT_DEBUG_CHANNEL(ddraw);

#define CALC_ITEM(r,c) \
  m->_##r##c = \
  m1->_##r##1 * m2->_1##c + \
  m1->_##r##2 * m2->_2##c + \
  m1->_##r##3 * m2->_3##c + \
  m1->_##r##4 * m2->_4##c

void MatrixMultiply(LPD3DMATRIX m, LPD3DMATRIX m1, LPD3DMATRIX m2)
{
  CALC_ITEM(1,1); CALC_ITEM(1,2); CALC_ITEM(1,3); CALC_ITEM(1,4);
  CALC_ITEM(2,1); CALC_ITEM(2,2); CALC_ITEM(2,3); CALC_ITEM(2,4);
  CALC_ITEM(3,1); CALC_ITEM(3,2); CALC_ITEM(3,3); CALC_ITEM(3,4);
  CALC_ITEM(4,1); CALC_ITEM(4,2); CALC_ITEM(4,3); CALC_ITEM(4,4);
}

#undef CALC_ITEM

#define CALC_ITEM(c) \
  v->u1.x * m->_1##c + \
  v->u2.y * m->_2##c + \
  v->u3.z * m->_3##c + \
  m->_4##c

D3DVALUE MatrixApply(LPD3DVECTOR r, LPD3DVECTOR v, LPD3DMATRIX m)
{
  r->u1.x = CALC_ITEM(1);
  r->u2.y = CALC_ITEM(2);
  r->u3.z = CALC_ITEM(3);
  return CALC_ITEM(4); /* homogenous W */
}

#undef CALC_ITEM

static D3DCOLOR def_diffuse = 0xffffffff;
static D3DCOLOR def_specular = 0x00000000;

#define ADVANCE(p,s) p = (((LPBYTE)(p)) + s)
#define INIT_ADVANCE(p,c) ADVANCE((p).lpvData, (p).dwStride*(c))
#define NEXT_ADVANCE(p) ADVANCE((p).lpvData, (p).dwStride)

HRESULT ProcessStridedVertices(LPD3DDRAWPRIMITIVESTRIDEDDATA lpDst, DWORD dwDstFVF,
                               DWORD dwVertexOp, DWORD dwDstIndex, DWORD dwCount,
                               LPD3DDRAWPRIMITIVESTRIDEDDATA lpSrc, DWORD dwSrcFVF,
                               DWORD dwSrcIndex, LPDIRECT3DDEVICE7 lpD3DDevice,   
                               DWORD dwFlags, LPD3DHVERTEX lpHOut, LPD3DCLIPSTATUS lpClip,
                               LPD3DMATRIX clip_matrix)
{
  ICOM_THIS(IDirect3DDeviceImpl, lpD3DDevice);
  D3DMATRIX mv, proj, all;
  D3DVECTOR vtmp;
  D3DVALUE left, top, width, height, w, rhw, *vsrc, *vdst;
  D3DCOLOR d, s, *dsrc, *ssrc, *ddst, *sdst;
  D3DDRAWPRIMITIVESTRIDEDDATA src, dst;
  BOOL light;
  unsigned tex, c, u;

  TRACE("This=%p, srcFVF=%lx, dstFVF=%lx, vop=%lx, dwFlags=%lx\n", This,
        dwSrcFVF, dwDstFVF, dwVertexOp, dwFlags);

  if (!(dwVertexOp & D3DVOP_TRANSFORM))
    return DDERR_INVALIDPARAMS;
  if (dwVertexOp & ~D3DVOP_TRANSFORM) {
    FIXME("can't handle VOP %lx yet\n", dwVertexOp & ~D3DVOP_TRANSFORM);
  }

  if ((dwDstFVF & D3DFVF_POSITION_MASK) != D3DFVF_XYZRHW)
    return D3DERR_INVALIDVERTEXFORMAT; /* ? */
  if (dwSrcFVF) {
    if ((dwSrcFVF & D3DFVF_POSITION_MASK) == D3DFVF_XYZRHW)
      return D3DERR_INVALIDVERTEXFORMAT; /* ? */
    if ((dwSrcFVF & D3DFVF_POSITION_MASK) != D3DFVF_XYZ) {
      FIXME("can't handle vertex blending\n");
    }
    if ((dwDstFVF & D3DFVF_TEXCOUNT_MASK) && !(dwFlags & D3DPV_DONOTCOPYDATA) &&
        (dwSrcFVF & D3DFVF_TEXCOUNT_MASK) != (dwDstFVF & D3DFVF_TEXCOUNT_MASK)) {
      ERR("different number of texture coordinates\n");
      return D3DERR_INVALIDVERTEXFORMAT; /* ? */
    }
  }

  tex = (dwFlags & D3DPV_DONOTCOPYDATA) ? 0 : ((dwDstFVF & D3DFVF_TEXCOUNT_MASK) >> D3DFVF_TEXCOUNT_SHIFT);

  /* Combine view and world matrices */
  MatrixMultiply(&mv,
		 &This->d3dp.xfrm[D3DTRANSFORMSTATE_WORLD],
		 &This->d3dp.xfrm[D3DTRANSFORMSTATE_VIEW]);
  /* Combine the result with projection matrix */
  MatrixMultiply(&proj, &mv,
		 &This->d3dp.xfrm[D3DTRANSFORMSTATE_PROJECTION]);
  /* Combine the result with clip matrix */
  if (clip_matrix) {
    MatrixMultiply(&all, &proj, clip_matrix);
  }
  else memcpy(&all, &proj, sizeof(all));

  /* Get viewport */
  left = This->d3dp.viewport.dwX;
  top = This->d3dp.viewport.dwY;
  width = This->d3dp.viewport.dwWidth / 2.0;
  height = This->d3dp.viewport.dwHeight / 2.0;

  /* Get initial pointers */
  src = *lpSrc;
  INIT_ADVANCE(src.position, dwSrcIndex);
  if ((dwSrcFVF & D3DFVF_NORMAL) || !dwSrcFVF)
    INIT_ADVANCE(src.normal, dwSrcIndex);
  if ((dwSrcFVF & D3DFVF_DIFFUSE) || !dwSrcFVF)
    INIT_ADVANCE(src.diffuse, dwSrcIndex);
  else {
    src.diffuse.lpvData = &def_diffuse;
    src.diffuse.dwStride = 0;
  }
  if ((dwSrcFVF & D3DFVF_SPECULAR) || !dwSrcFVF)
    INIT_ADVANCE(src.specular, dwSrcIndex);
  else {
    src.specular.lpvData = &def_specular;
    src.specular.dwStride = 0;
  }
  for (u=0; u<tex; u++)
    INIT_ADVANCE(src.textureCoords[u], dwSrcIndex);

  dst = *lpDst;
  INIT_ADVANCE(dst.position, dwDstIndex);
  if (dwDstFVF & D3DFVF_NORMAL)
    INIT_ADVANCE(dst.normal, dwDstIndex);
  if (dwDstFVF & D3DFVF_DIFFUSE)
    INIT_ADVANCE(dst.diffuse, dwDstIndex);
  if (dwDstFVF & D3DFVF_SPECULAR)
    INIT_ADVANCE(dst.specular, dwDstIndex);
  for (u=0; u<tex; u++)
    INIT_ADVANCE(dst.textureCoords[u], dwDstIndex);
  light = ((dwDstFVF & (D3DFVF_DIFFUSE|D3DFVF_SPECULAR)) &&
           ((dwVertexOp & D3DVOP_LIGHT) || !(dwFlags & D3DPV_DONOTCOPYDATA)));

  /* Process vertices */
  TRACE("transforming %ld vertices\n", dwCount);
  for (c=0; c<dwCount; c++) {
    /* Transform */
    vsrc = src.position.lpvData;
    vdst = dst.position.lpvData;
    TRACE("src(%p): %f, %f, %f => %p\n", vsrc, vsrc[0], vsrc[1], vsrc[2], vdst);
    w = MatrixApply(&vtmp, (LPD3DVECTOR)vsrc, &all);
    rhw = 1.0/w;
    /* FIXME: clip checking */
    /* Convert to screen coordinates */
    vdst[0] = (1.0 + vtmp.u1.x * rhw) * width + left;
    vdst[1] = (1.0 - vtmp.u2.y * rhw) * height + top;
    vdst[2] = (vtmp.u3.z * rhw); /* FIXME? */
    vdst[3] = rhw;
    TRACE("dst(%p): %f, %f, %f, %f\n", vdst, vdst[0], vdst[1], vdst[2], vdst[3]);
    NEXT_ADVANCE(src.position);
    NEXT_ADVANCE(dst.position);

    if (lpClip) {
      if (vdst[0] < lpClip->minx) lpClip->minx = vdst[0];
      if (vdst[0] > lpClip->maxx) lpClip->maxx = vdst[0];
      if (vdst[1] < lpClip->miny) lpClip->miny = vdst[1];
      if (vdst[1] > lpClip->maxy) lpClip->maxy = vdst[1];
      if (vdst[2] < lpClip->minz) lpClip->minz = vdst[2];
      if (vdst[2] > lpClip->maxz) lpClip->maxz = vdst[2];
    }

    /* Light (or just copy colors) */
    if (light) {
      dsrc = src.diffuse.lpvData;
      ssrc = src.specular.lpvData;
      ddst = dst.diffuse.lpvData;
      sdst = dst.specular.lpvData;
      TRACE("src color: %08lx, %08lx\n", *dsrc, *ssrc);
      if (dwVertexOp & D3DVOP_LIGHT) {
        FIXME("light vertices in software\n");
        d = *dsrc; s = *ssrc;
      } else {
        d = *dsrc; s = *ssrc;
      }
      TRACE("dst color: %08lx, %08lx\n", d, s);
      NEXT_ADVANCE(src.diffuse);
      NEXT_ADVANCE(src.specular);
      if (dwDstFVF & D3DFVF_DIFFUSE) {
        *((LPD3DCOLOR)dst.diffuse.lpvData) = d;
        NEXT_ADVANCE(dst.diffuse);
      }
      if (dwDstFVF & D3DFVF_SPECULAR) {
        *((LPD3DCOLOR)dst.specular.lpvData) = s;
        NEXT_ADVANCE(dst.specular);
      }
    }

    /* Copy texture coordinates */
    for (u=0; u<tex; u++) {
      memcpy(dst.textureCoords[u].lpvData,
             src.textureCoords[u].lpvData,
             2*sizeof(D3DVALUE));
      NEXT_ADVANCE(src.textureCoords[u]);
      NEXT_ADVANCE(dst.textureCoords[u]);
    }
  }

  return D3D_OK;
}
