/*==========================================================================
 *
 *  Copyright (C) 1995 Microsoft Corporation. All Rights Reserved.
 *
 *  File: polytest.c
 *
 ***************************************************************************/

#include "pch.cpp"
#pragma hdrstop

#include "rend.h"
#include "globals.h"
#include "util.h"
#include "d3dsphr.h"
#include "polytest.h"

#define MAX_OBJECTS 20
#define CAMERA_POS_POLYGON 7.0f
#define CAMERA_POS_INTERSECTION 2.0f

/*
 * Global to keep track of execute buffer
 */
static RendExecuteBuffer *lpExBuf;

/*
 * Global for light
 */
static RendLight *prlight;

/*
 * Global projection and view matrices
 */
static RendMatrix *pProj;
static RendMatrix *pView;
static D3DMATRIX proj = {
    D3DVAL(2.0), D3DVAL(0.0), D3DVAL(0.0), D3DVAL(0.0),
    D3DVAL(0.0), D3DVAL(2.0), D3DVAL(0.0), D3DVAL(0.0),
    D3DVAL(0.0), D3DVAL(0.0), D3DVAL(1.0), D3DVAL(1.0),
    D3DVAL(0.0), D3DVAL(0.0), D3DVAL(-1.0), D3DVAL(0.0)
};
static D3DMATRIX view = {
    D3DVAL(1.0), D3DVAL(0.0), D3DVAL(0.0), D3DVAL(0.0),
    D3DVAL(0.0), D3DVAL(1.0), D3DVAL(0.0), D3DVAL(0.0),
    D3DVAL(0.0), D3DVAL(0.0), D3DVAL(1.0), D3DVAL(0.0),
    D3DVAL(0.0), D3DVAL(0.0), D3DVAL(7.0), D3DVAL(1.0)
};

/*
 * A structure which holds the object's data
 */
static struct {
    LPD3DVERTEX lpV;		           /* object's vertices    */
    LPD3DTRIANGLE lpTri;		   /* object's triangles   */
    int num_vertices, num_faces;
} objData;

typedef struct _orderElt {
    float	z;
    RendExecuteBuffer *exeBuff;
} orderElt;
static orderElt order[MAX_OBJECTS]; 

static int NumSpheres;
typedef struct tagSphereData {
    float vx, vy, vz;
    float rx, ry, rz;
    int age;
    RendMatrix *pMr;
    RendMatrix *pMdr;
    RendMatrix *pMp;
    RendMatrix *pMdp;
    RendMatrix *pM;
    RendTexture *pTex;
    RendMaterial *lpmat;
    RendExecuteBuffer *lpSetWorldExeBuf;
} SphereData;
static SphereData* sphere;

static float D, R, DMINUSR, DV, DR, DEPTH;
static UINT ORDER;

void
tick_sphere(int n)
{
    float x, y, z;
    D3DMATRIX Mp, Mdr, Mdp;
    sphere[n].pMp->Get(&Mp);
    x = (float)Mp._41;
    y = (float)Mp._42;
    z = (float)Mp._43;
    if (x > DMINUSR || x < -DMINUSR) {
	sphere[n].vx = -sphere[n].vx;
	sphere[n].rz = -sphere[n].rz;
	sphere[n].ry = -sphere[n].ry;
	++sphere[n].age;
    }
    if (y > DMINUSR || y < -DMINUSR) {
	sphere[n].vy = -sphere[n].vy;
	sphere[n].rz = -sphere[n].rz;
	sphere[n].rx = -sphere[n].rx;
	++sphere[n].age;
    }
    if (z > DEPTH || z < -DEPTH) {
	sphere[n].vz = -sphere[n].vz;
	sphere[n].rx = -sphere[n].rx;
	sphere[n].ry = -sphere[n].ry;
	++sphere[n].age;
    }
    if (sphere[n].age) {
	MakeRotMatrix(&Mdr, sphere[n].rx, sphere[n].ry, sphere[n].rz);
	sphere[n].pMdr->Set(&Mdr);
	MakePosMatrix(&Mdp, sphere[n].vx, sphere[n].vy, sphere[n].vz);
	sphere[n].pMdp->Set(&Mdp);
	sphere[n].age = 0;
    }
}

static int __cdecl compareZ(const void* p1, const void* p2)
{
    orderElt* h1 = (orderElt*) p1;
    orderElt* h2 = (orderElt*) p2;
    if (ORDER == FRONT_TO_BACK) {
	if (h1->z > h2->z) return -1;
	if (h1->z < h2->z) return 1;
    } else {
	if (h1->z < h2->z) return -1;
	if (h1->z > h2->z) return 1;
    }
    return 0;
}

void
sortObjects(void)
{
    int i;
    D3DMATRIX M;
    
    for (i = 0; i < NumSpheres; i++) {
	sphere[i].pMp->Get(&M);
	order[i].z = (float)M._43;
	order[i].exeBuff = sphere[i].lpSetWorldExeBuf;
    }
    qsort(order, NumSpheres, sizeof(orderElt), compareZ);
}

/*
 * Each frame, renders the scene and calls mod_buffer to modify the object
 * for the next frame.
 */
BOOL
RenderScenePoly(RendWindow *prwin, LPD3DRECT lpExtent)
{
    D3DRECT extent;
    int j;

    /*
     * Execute the instruction buffer
     */
    if (!prwin->BeginScene())
        return FALSE;
    
    if (ORDER != NO_SORT)
        sortObjects();
    
    for (j = 0; j < NumSpheres; j++) {
 	tick_sphere(j);
        
	if (ORDER == NO_SORT) {
	    /* we can also be sure this is the poly throughput test */
            if (!prwin->Execute(sphere[j].lpSetWorldExeBuf))
		return FALSE;
	    if (!prwin->Execute(lpExBuf))
		return FALSE;
	} else {
	    /* must be intersection test */
	    if (!prwin->Execute(order[j].exeBuff))
		return FALSE;
	    if (!prwin->ExecuteClipped(lpExBuf))
		return FALSE;
	}
    }
    
    if (!prwin->EndScene(&extent))
        return FALSE;
    
    *lpExtent = extent;
    return TRUE;
}

/*
 * Release the memory allocated for the scene and all D3D objects created.
 */
void
ReleaseViewPoly(void)
{
    int i;
    
    RELEASE(lpExBuf);
    RELEASE(prlight);
    
    for (i = 0; i < NumSpheres; i++) {
	RELEASE(sphere[i].lpmat);
    }
    
    if (objData.lpV)
        free(objData.lpV);
    if (objData.lpTri)
        free(objData.lpTri);
    free(sphere);
}

unsigned long
init_spheres(RendWindow *prwin, int n)
{
    D3DMATRIX Mp, Mdp, Mdr;
    int i;
    
    InitRandom();
    sphere = (SphereData*)malloc(n*sizeof(SphereData));
    memset(sphere, 0, n*sizeof(SphereData));
    for (i = 0; i < n; i++) {
	MakePosMatrix(&Mp, (float)DMINUSR - Random(2 * DMINUSR),
		      (float)DMINUSR - Random(2 * DMINUSR),
		      (float)-DEPTH + Random(2 * DEPTH));
	MAKE_REND_MATRIX(prwin, sphere[i].pMp, Mp);
	sphere[i].vx = (float)DV - Random(2 * DV);
	sphere[i].vy = (float)DV - Random(2 * DV);
	sphere[i].vz = (float)DV - Random(2 * DV);
	MakePosMatrix(&Mdp, sphere[i].vx, sphere[i].vy, sphere[i].vz);
	MAKE_REND_MATRIX(prwin, sphere[i].pMdp, Mdp);
	sphere[i].rx = (float)DR - Random(2 * DR);
	sphere[i].ry = (float)DR - Random(2 * DR);
	sphere[i].rz = (float)DR - Random(2 * DR);
	MakeRotMatrix(&Mdr, sphere[i].rx, sphere[i].ry, sphere[i].rz);
	MAKE_REND_MATRIX(prwin, sphere[i].pMdr, Mdr);
	MAKE_REND_MATRIX(prwin, sphere[i].pMr, dmIdentity);
	MAKE_REND_MATRIX(prwin, sphere[i].pM, dmIdentity);
    }
    return 1;
}

/*
 * Builds the scene and initializes the execute buffer for rendering.  Returns 0 on failure.
 */

#define ALPHA_COLOR_COUNT 3
static D3DCOLORVALUE dcvAlphaColors[ALPHA_COLOR_COUNT] =
{
    1.0f, 0.0f, 0.0f, 0.25f,
    0.0f, 1.0f, 0.0f, 0.50f,
    0.0f, 0.0f, 1.0f, 0.75f
};

unsigned long
InitViewPoly(RendWindow *prwin,
             int NumTextures, RendTexture **pprtex,
             UINT num, UINT rings, UINT segs, UINT order,
             float radius, float d, float depth,
	     float dv, float dr, BOOL bAlpha)
{
    /* Pointers into the exectue buffer. */
    LPVOID lpBufStart, lpInsStart, lpPointer;
    RendExecuteBuffer *lpExCmdBuf;
    size_t size;
    int i;

    D3DCOLORVALUE dcol;
    D3DVECTOR dvec;

    /* This sucks, but I'm tired */
    D = d;
    R = radius;
    DMINUSR = d - radius;
    DV = dv;
    DR = dr;
    DEPTH = depth;
    ORDER = order;

    NumSpheres = (int)num;

    /*
     * Generate the sphere.
     */
    if (!(GenerateSphere(R, rings, segs, (float)1.0, (float)1.0,
                         (float)1.0, &objData.lpV, &objData.lpTri,
                         &objData.num_vertices, &objData.num_faces)))
        return 0L;
    
    /*
     * Set the view and projection matrices, make the world matricies
     */
    if (order == NO_SORT) {
	view._43 = CAMERA_POS_POLYGON;
    } else {
	view._43 = CAMERA_POS_INTERSECTION;
    }
    
    MAKE_REND_MATRIX(prwin, pView, view);
    MAKE_REND_MATRIX(prwin, pProj, proj);

    /*
     * Create buffers for world matrix set commands
     */
    init_spheres(prwin, NumSpheres);
    for (i = 0; i < NumSpheres; i++) {
	
	/*
	 * Create a material, set its description and obtain a handle to it.
	 */
        sphere[i].lpmat = prwin->NewMaterial(32);
        if (sphere[i].lpmat == NULL)
	    return 0L;
        if (bAlpha)
        {
            dcol = dcvAlphaColors[i % ALPHA_COLOR_COUNT];
        }
        else
        {
            dcol.r = D3DVAL(1.0);
            dcol.g = D3DVAL(1.0);
            dcol.b = D3DVAL(1.0);
            dcol.a = D3DVAL(1.0);
        }
        sphere[i].lpmat->SetDiffuse(&dcol);
        sphere[i].lpmat->SetSpecular(&dcol, 40.0f);
        sphere[i].pTex = pprtex[i % NumTextures];
        sphere[i].lpmat->SetTexture(sphere[i].pTex);

        size = 0;
        size += sizeof(D3DINSTRUCTION) * 5;
        size += sizeof(D3DMATRIXMULTIPLY) * 3;
        size += sizeof(D3DSTATE) * 4;
        sphere[i].lpSetWorldExeBuf =
            prwin->NewExecuteBuffer(size, stat.uiExeBufFlags);
        if (sphere[i].lpSetWorldExeBuf == NULL)
	    return 0L;
        lpBufStart = sphere[i].lpSetWorldExeBuf->Lock();
        if (lpBufStart == NULL)
    	    return 0L;
        memset(lpBufStart, 0, size);
        lpPointer = lpBufStart;
        lpInsStart = lpPointer;
        OP_MATRIX_MULTIPLY(3, lpPointer);
	    MATRIX_MULTIPLY_REND_DATA(sphere[i].pMdr, sphere[i].pMr,
                                      sphere[i].pMr, lpPointer);
	    MATRIX_MULTIPLY_REND_DATA(sphere[i].pMdp, sphere[i].pMp,
                                      sphere[i].pMp, lpPointer);
	    MATRIX_MULTIPLY_REND_DATA(sphere[i].pMr, sphere[i].pMp,
                                      sphere[i].pM, lpPointer);
        OP_STATE_TRANSFORM(1, lpPointer);
            STATE_DATA(D3DTRANSFORMSTATE_WORLD, sphere[i].pM->Handle(),
                       lpPointer);
	OP_STATE_LIGHT(1, lpPointer);
	    STATE_DATA(D3DLIGHTSTATE_MATERIAL, sphere[i].lpmat->Handle(),
                       lpPointer);
	OP_STATE_RENDER(2, lpPointer);
	    STATE_DATA(D3DRENDERSTATE_TEXTUREHANDLE,
                       (sphere[i].pTex != NULL ? sphere[i].pTex->Handle() : 0),
                       lpPointer);
	    STATE_DATA(D3DRENDERSTATE_WRAPU, TRUE, lpPointer);
        OP_EXIT(lpPointer);
        /*
         * Setup the execute data describing the buffer
         */
        sphere[i].lpSetWorldExeBuf->Unlock();
        if (!sphere[i].lpSetWorldExeBuf->Process())
        {
            return 0L;
        }
    }
    /*
     * Create a buffer for matrix set commands etc.
     */
    size = 0;
    size += sizeof(D3DINSTRUCTION) * 6;
    size += sizeof(D3DSTATE) * 6;
    lpExCmdBuf = prwin->NewExecuteBuffer(size, stat.uiExeBufFlags);
    if (lpExCmdBuf == NULL)
	return 0L;
    lpBufStart = lpExCmdBuf->Lock();
    if (lpBufStart == NULL)
	return 0L;
    memset(lpBufStart, 0, size);
    lpPointer = lpBufStart;

    lpInsStart = lpPointer;
    OP_STATE_TRANSFORM(2, lpPointer);
        STATE_DATA(D3DTRANSFORMSTATE_VIEW, pView->Handle(), lpPointer);
        STATE_DATA(D3DTRANSFORMSTATE_PROJECTION, pProj->Handle(), lpPointer);
    OP_STATE_LIGHT(1, lpPointer);
        STATE_DATA(D3DLIGHTSTATE_AMBIENT, RGBA_MAKE(10, 10, 10, 10),
                   lpPointer);
    OP_STATE_RENDER(3, lpPointer);
        STATE_DATA(D3DRENDERSTATE_BLENDENABLE, bAlpha, lpPointer);
        STATE_DATA(D3DRENDERSTATE_SRCBLEND, D3DBLEND_SRCALPHA, lpPointer);
        STATE_DATA(D3DRENDERSTATE_DESTBLEND, D3DBLEND_INVSRCALPHA, lpPointer);
    OP_EXIT(lpPointer);
    /*
     * Setup the execute data describing the buffer
     */
    lpExCmdBuf->Unlock();
    if (!lpExCmdBuf->Process())
    {
        return 0L;
    }
    prwin->BeginScene();
    prwin->Execute(lpExCmdBuf);
    prwin->EndScene(NULL);
    /*
     * We are done with the command buffer.
     */
    lpExCmdBuf->Release();

    /*
     * Create the object execute buffer
     */
    size = sizeof(D3DVERTEX) * objData.num_vertices;
    size += sizeof(D3DPROCESSVERTICES) * 1;
    size += sizeof(D3DINSTRUCTION) * 4;
    size += sizeof(D3DTRIANGLE) * objData.num_faces;
    lpExBuf = prwin->NewExecuteBuffer(size, stat.uiExeBufFlags);
    if (lpExBuf == NULL)
        return 0L;

    /*
     * lock it so it can be filled
     */
    lpBufStart = lpExBuf->Lock();
    if (lpBufStart == NULL)
        return 0L;
    memset(lpBufStart, 0, size);
    lpPointer = lpBufStart;

    VERTEX_DATA(objData.lpV, objData.num_vertices, lpPointer);
    /*
     * Save the location of the first instruction and add instructions to 
     * execute buffer.
     */
    lpInsStart = lpPointer;
    OP_PROCESS_VERTICES(1, lpPointer);
        PROCESSVERTICES_DATA(D3DPROCESSVERTICES_TRANSFORMLIGHT, 0,
                             objData.num_vertices, lpPointer);
    /*
     * Make sure that the triangle data (not OP) will be QWORD aligned
     */
    if (QWORD_ALIGNED(lpPointer)) {
	OP_NOP(lpPointer);
    }
    OP_TRIANGLE_LIST(objData.num_faces, lpPointer);
        TRIANGLE_LIST_DATA(objData.lpTri, objData.num_faces, lpPointer);
    OP_EXIT(lpPointer);
    /*
     * Setup the execute data describing the buffer
     */
    lpExBuf->Unlock();
    lpExBuf->SetData(objData.num_vertices,
                     (ULONG)((char*)lpInsStart - (char*)lpBufStart),
                     (ULONG)((char*)lpPointer - (char*)lpInsStart));
    if (!lpExBuf->Process())
    {
        return 0L;
    }

    /*
     *  Create the light
     */
    prlight = prwin->NewLight(REND_LIGHT_DIRECTIONAL);
    if (prlight == NULL)
        return 0L;
    dcol.r = 0.9f;
    dcol.g = 0.9f;
    dcol.b = 0.9f;
    dcol.a = 1.0f;
    prlight->SetColor(&dcol);
    dvec.x = 0.0f;
    dvec.y = 0.0f;
    dvec.z = 1.0f;
    prlight->SetVector(&dvec);

    return NumSpheres * objData.num_faces;
}