/*****************************************************************************
* Color of the scan-line pixel evaluation algorithms.  		             *
******************************************************************************
* (C) Gershon Elber, Technion, Israel Institute of Technology                *
******************************************************************************
* Written by:  Bassarab Dmitri & Plavnik Michael       Ver 0.2, Apr. 1995    *
*****************************************************************************/

#include "program.h"
#include "shadow.h"

#include "debug.h"

static void ColorAdd(ColorType c,
                     const ColorType l,
                     const ColorType o,
                     const IntensivityStruct *i);

/*****************************************************************************
* DESCRIPTION:                                                               M
*   Computes intensivity diffuse and specular values using specular model of M
*   illumination (see Foily, Van Dam). Differs between point and vector      M
*   viewer, point and vector light sources, which is defined in Light object M
*   and by calling IS_VIEWER_POINT() function.                               M
*                                                                            *
* PARAMETERS:                                                                M
*   p:       IN, point for which intensivity is computing.                   M
*   n:       IN, normal to the surface in the point "p".                     M
*   l:       IN, pointer to the light source object.                         M
*   o:       IN, pointer to the object with surface characteristics.         M
*   i:       OUT, pointer to resulting intensivity object.                   M
*                                                                            *
* RETURN VALUE:                                                              M
*   void                                                                     M
*                                                                            *
* KEYWORDS:                                                                  M
*   LightIntensivity, specular, shading                                      M
*****************************************************************************/
void LightIntensivity(const PointType p,
                      const NormalType n,
                      LightStruct *l,
                      const ObjectStruct *o,
                      IntensivityStruct *i)
{
    RealType CosTheta, CosAlpha, *Light, *Sight,  /* For efficiency reasons. */
	KDiffuse = 0.4,
	KSpecular = 0.4;
    static NormalType Normal;

    if (l -> type == POINT_LIGHT) {
        static PointType LightStorage;

        Light = &LightStorage[0];                  /* We elliminate copying. */
        PT_SUB(Light, l -> where, p);
        PT_NORMALIZE(Light);
    }
    else
        Light = l -> where;

    if (IS_VIEWER_POINT()) {
        static PointType SightStorage;

        Sight = &SightStorage[0];
        PT_SUB(Sight, Context.Viewer, p);
        PT_NORMALIZE(Sight);
    }
    else
        Sight = Context.Viewer;

    i -> diff = i -> spec = 0.0;

    /* Minus corrects assumed direction - "to the center of sphere". */
    PT_COPY(Normal, n);
    PT_SCALE(Normal, -1.0);

    CosTheta = DOT_PROD(Light, Normal);
    if (CosTheta > EPSILON) {       /* Light passes from behind of the poly. */
        static PointType Mirrored;

        i -> diff = KDiffuse * CosTheta;
        PT_SCALE(Normal, 2 * CosTheta);
        PT_SUB(Mirrored, Normal, Light);
        CosAlpha = DOT_PROD(Sight, Mirrored);
        if (CosAlpha > EPSILON)
            i -> spec = KSpecular * pow(CosAlpha, o -> power);
    }
}

/*****************************************************************************
* DESCRIPTION:                                                               *
*   Aux. function which adds to resulting color light source color scaled by *
*   diffuse-specualr composition.                                            *
*                                                                            *
* PARAMETERS:                                                                *
*   c:      IN OUT, resulting color.                                         *
*   l:      IN, light source color.                                          *
*   o:      IN, object color.                                                *
*   i:      IN, pointer to an intensivity object.                            *
*                                                                            *
* RETURN VALUE:                                                              *
*   void                                                                     *
*****************************************************************************/
static void ColorAdd(ColorType c,
                     const ColorType l,
                     const ColorType o,
                     const IntensivityStruct *i)
{
    c[R] += l[R] * (o[R] * i -> diff + i -> spec);
    c[G] += l[G] * (o[G] * i -> diff + i -> spec);
    c[B] += l[B] * (o[B] * i -> diff + i -> spec);
}

/*****************************************************************************
* DESCRIPTION:                                                               M
*   For scan liny "y" and pixel "x" in it computes color value in [0, 1]     M
*   RGB format.                                                              M
*                                                                            *
* PARAMETERS:                                                                M
*   x:       IN, scan line pixel position.                                   M
*   y:       IN, scan line number.                                           M
*   z:       IN OUT, array of scan line data(may be corrupted as side effect)M
*   r:       OUT, resulting color.                                           M
*                                                                            *
* RETURN VALUE:                                                              M
*   void                                                                     M
*                                                                            *
* KEYWORDS:                                                                  M
*   ColorEval, RGB, intensivity, scan line, shading                          M
*****************************************************************************/
void ColorEval(int x, int y, ZPointStruct *z, ColorType r)
{
    ObjectStruct
	*o = z -> flat -> object;
    ColorType Orig;
    PointType p;
    int j;

    if (o -> noShade){         /* Take care of special objects (pure color). */
        PT_COPY(r, o -> color);
	return;
    }

    /* Initialize current image point and transform to object space. */
    p[X] = x;
    p[Y] = y;
    p[Z] = z -> value.z;
    MatMultVecby4by4(p, p, Context.InvMat);

    /* Transform normal to object space, using homogenious coordinates */
    /* interpolation.			        		       */
    PT_SCALE(z -> value.n, 1 / z -> value.w);

    /* Fill resulting color with (texture) color, or object color. */
    PT_COPY(r, o -> color);

    switch (o -> text.type) {
	case TEXTURE_TYPE_RSTR:
	    {
		PixelStruct
		    *p = ImageGetPixel(o -> text.image,
				       z -> value.v / z -> value.w,
				       z -> value.u / z -> value.w);

		r[R] = p -> r;
		r[G] = p -> g;
		r[B] = p -> b;
		PT_SCALE(r, 1. / 0xff);
	    }
	    break;
	case TEXTURE_TYPE_PROC:
            PT_NORMALIZE(z -> value.n);
	    p[0] *= o -> text.tScale[0];
	    p[1] *= o -> text.tScale[1];
	    p[2] *= o -> text.tScale[2];
            (*o -> text.vTexture) (p, z -> value.n, r, &o -> transp);
	    break;
	case TEXTURE_TYPE_SRF:
	    {
		CagdRType
		    u = o -> text.srfParamDomain[0][2] *
					(z -> value.u / z -> value.w) + 
					  o -> text.srfParamDomain[0][0],
		    v = o -> text.srfParamDomain[1][2] *
					(z -> value.v / z -> value.w) + 
					  o -> text.srfParamDomain[1][0],
		    *Pt = CagdSrfEval(o -> text.srf, u, v),
		    t = CAGD_IS_RATIONAL_SRF(o -> text.srf) ? Pt[1] / Pt[0]
							    : Pt[1];

		switch (o -> text.srfFunc) {
		    case STEXTURE_FUNC_SQRT:
		        t = sqrt(fabs(t));
			break;
		    case STEXTURE_FUNC_ABS:
			t = fabs(t);
			break;
		}

		t = (t - o -> text.srfScaleMinMax[0]) /
		   (o -> text.srfScaleMinMax[1] - o -> text.srfScaleMinMax[0]);
		t = BOUND(t, 0.0, 1.0);

		if (o -> text.srfScale != NULL) {
		    PixelStruct
			*p = ImageGetPixel(o -> text.srfScale, t, 0);

		    r[R] = p -> r;
		    r[G] = p -> g;
		    r[B] = p -> b;
		    PT_SCALE(r, 1. / 0xff);
		}
		else {
		    RealType
			t1 = 1.0 - t;

		    /* Use our own scale: */
		    r[R] = t * t;
		    r[G] = 2 * t * t1;
		    r[B] = t1 * t1;
		}
	    }
	    break;
    }

    PT_COPY(Orig, r);
    PT_SCALE(r, Options.Ambient);     /* Compose ambient color in resulting. */
    if (Options.ShadeModel == PHONG) /* Ensure that normal is a real normal. */
        PT_NORMALIZE(z -> value.n);

    /* Add to the resulting color diffuse and specular components from all */
    /* light sources, check shadows by the way.			           */
    for (j = 0; j < Context.Lights.n; j++) {
        if (Options.Shadows &&
	    Context.Lights.src[j].shadow &&
	    IsInShadow(z -> flat, j, x))
            continue;
        if (Options.ShadeModel == PHONG) {
            IntensivityStruct i;

            LightIntensivity(p, z -> value.n, &Context.Lights.src[j], o, &i);
            ColorAdd(r, Context.Lights.src[j].color, Orig, &i);
        }
	else {
            if (Options.ShadeModel == GOURAUD) {
                z -> value.i[j].diff /= z -> value.w;
                z -> value.i[j].spec /= z -> value.w;
            }
            ColorAdd(r, Context.Lights.src[j].color, Orig, &z -> value.i[j]);
        }
    }

    MINM(r[R], 1);                   /* Ensure that coor is [0, 1] interval. */
    MINM(r[G], 1);
    MINM(r[B], 1);
}
