/******************************************************************************
* CagdAIso.c - adaptive isoline surface extraction algorithm.		      *
*******************************************************************************
* Written by Gershon Elber, Aug. 92.					      *
******************************************************************************/

#include "cagd_loc.h"

#define CONVEX_BLEND(v1, v2, b1, b2, t) { \
		CagdRType \
		    Blend = -b1 / ( b2 - b1 ); \
		t = v2 * Blend + v1 * (1.0 - Blend); \
	    }

static int MinSubdivLevel = 1;

static CagdCrvStruct *CagdAdapIsoExtractAux(int Level,
				     CagdSrfStruct *Srf, CagdSrfStruct *NSrf,
				     CagdCrvStruct *Crv1, CagdCrvStruct *NCrv1,
				     CagdCrvStruct *Crv2, CagdCrvStruct *NCrv2,
				     CagdRType Crv1Param, CagdRType Crv2Param,
				     CagdSrfDirType Dir, CagdRType Eps2,
				     CagdBType FullIso, CagdBType SinglePath);

/******************************************************************************
* Sets minimum level of subdivision forced in the adaptive iso extraction.    *
******************************************************************************/
void CagdSetAdapIsoExtractMinLevel(int MinLevel)
{
    MinSubdivLevel = MinLevel;
}

/******************************************************************************
* Extract a valid coverage set of isolines from the given surface in the      *
* given direction and epsilon.						      *
*   If FullIso is TRUE, all extracted isocurves are spanning the entire       *
* parametric domain.							      *
*   If SinglePath is TRUE, the entire coverage is going to be a single curve  *
*   If NSrf != NULL, every second curve will be a vector field curve rep. the *
* unnormalized normal for the previous Euclidean curve.	This mode disable the *
* SinglePath mode.							      *
******************************************************************************/
CagdCrvStruct *CagdAdapIsoExtract(CagdSrfStruct *Srf, CagdSrfStruct *NSrf,
				  CagdSrfDirType Dir,
				  CagdRType Eps, CagdBType FullIso,
				  CagdBType SinglePath)
{
    CagdBType
	SrfBezier = FALSE;
    CagdRType Crv1Param, Crv2Param;
    CagdCrvStruct *Crv1, *Crv2, *NCrv1, *NCrv2, *AllAdapIso, *TCrv;

    if (NSrf != NULL)
	SinglePath = FALSE;

    switch (Srf -> GType) {
	case CAGD_SBEZIER_TYPE:
	    Srf = CnvrtBezier2BsplineSrf(Srf);
	    SrfBezier = TRUE;
	    break;
	case CAGD_SBSPLINE_TYPE:
	    break;
	default:
	    FATAL_ERROR(CAGD_ERR_WRONG_SRF);
	    break;
    }

    switch (Dir) {
	case CAGD_CONST_U_DIR:
	    Crv1Param = Srf -> GType == CAGD_SBSPLINE_TYPE ? 
		Srf -> UKnotVector[0] + EPSILON : EPSILON;
	    Crv2Param = Srf -> GType == CAGD_SBSPLINE_TYPE ? 
		Srf -> UKnotVector[Srf -> ULength +
				   Srf -> UOrder - 1] - EPSILON
		: 1.0 - EPSILON;
	    break;
	case CAGD_CONST_V_DIR:
	    Crv1Param = Srf -> GType == CAGD_SBSPLINE_TYPE ? 
		Srf -> VKnotVector[0] + EPSILON : EPSILON;
	    Crv2Param = Srf -> GType == CAGD_SBSPLINE_TYPE ? 
		Srf -> VKnotVector[Srf -> VLength +
				   Srf -> VOrder - 1] - EPSILON
		: 1.0 - EPSILON;
	    break;
	default:
	    FATAL_ERROR(CAGD_ERR_DIR_NOT_CONST_UV);
	    break;
    }

    Crv1 = CagdCrvFromSrf(Srf, Crv1Param, Dir);
    Crv2 = CagdCrvFromSrf(Srf, Crv2Param, Dir);
    if (NSrf != NULL) {
	NCrv1 = CagdCrvFromSrf(NSrf, Crv1Param, Dir);
	NCrv2 = CagdCrvFromSrf(NSrf, Crv2Param, Dir);
    }
    else
	NCrv1 = NCrv2 = NULL;

    /* Compute the adaptive iso curves. */
    AllAdapIso = CagdAdapIsoExtractAux(0, Srf, NSrf,
				       Crv1, NCrv1, Crv2, NCrv2,
				       Crv1Param, Crv2Param,
				       Dir, Eps * Eps, FullIso, SinglePath);

    /* Chain first and last iso curves that always span the entire domain. */
    if (AllAdapIso != NULL) {
	Crv1 -> Pnext = AllAdapIso;
	for (TCrv = AllAdapIso; TCrv -> Pnext != NULL; TCrv = TCrv -> Pnext);
	TCrv -> Pnext = Crv2;
    }
    else
	Crv1 -> Pnext = Crv2;

    if (NSrf != NULL) {
	NCrv1 -> Pnext = Crv1 -> Pnext;
	Crv1 -> Pnext = NCrv1;
	NCrv2 -> Pnext = Crv2 -> Pnext;
	Crv2 -> Pnext = NCrv2;
    }

    if (SrfBezier)
	CagdSrfFree(Srf);

    return Crv1;
}

/******************************************************************************
* An auxiliary function of CagdAdapIsoExtract. Computes the distance square   *
* between the given two curves, extract the regions that are far than that    *
* and recursively invoke this function with them.			      *
******************************************************************************/
static CagdCrvStruct *CagdAdapIsoExtractAux(int Level,
				     CagdSrfStruct *Srf, CagdSrfStruct *NSrf,
				     CagdCrvStruct *Crv1, CagdCrvStruct *NCrv1,
				     CagdCrvStruct *Crv2, CagdCrvStruct *NCrv2,
				     CagdRType Crv1Param, CagdRType Crv2Param,
				     CagdSrfDirType Dir, CagdRType Eps2,
				     CagdBType FullIso, CagdBType SinglePath)
{
    int i, KVLen;
    CagdCrvStruct
	*DiffCrv = CagdCrvSub(Crv1, Crv2),
	*DistCrv2 = CagdCrvDotProd(DiffCrv, DiffCrv);  /* Dist.^2 function. */
    CagdPointType
	PType = DistCrv2 -> PType;
    CagdRType Dist, *KV, LastT, DistCrv2Min, DistCrv2Max,
	**Points, *PtsW, *PtsX, LastDist,
	*Nodes = NULL;
    CagdBType CloseEnough, LastCloseEnough;
    CagdCrvStruct *TCrv,
	*AllAdapIsoTail = NULL,
	*AllAdapIso = NULL;

    CagdCrvFree(DiffCrv);

    /* Simple heuristic how much to refine DistCrv2: */
    KVLen = DistCrv2 -> Length * 2;
    DistCrv2Min = DistCrv2 -> KnotVector[0];
    DistCrv2Max = DistCrv2 -> KnotVector[DistCrv2 -> Order +
					 DistCrv2 -> Length - 1];
    KV = (CagdRType *) IritMalloc(sizeof(CagdRType) * KVLen);
    for (i = 0; i < KVLen; i++)
	KV[i] = DistCrv2Min + (i + 1) * (DistCrv2Max - DistCrv2Min) /
								(KVLen + 1);

    TCrv = BspCrvKnotInsertNDiff(DistCrv2, FALSE, KV, KVLen);
    IritFree((VoidPtr) KV);
    CagdCrvFree(DistCrv2);
    DistCrv2 = TCrv;

    Nodes = BspKnotNodes(DistCrv2 -> KnotVector,
			 DistCrv2 -> Length + DistCrv2 -> Order,
			 DistCrv2 -> Order);
    LastT = Nodes[0];
    Points = DistCrv2 -> Points,
    PtsW = Points[W],
    PtsX = Points[X],
    LastDist = (PType == CAGD_PT_E1_TYPE ? PtsX[0]
					 : (PtsX[0] / PtsW[0])) - Eps2;
    LastCloseEnough = LastDist < 0.0 && MinSubdivLevel <= Level;

    for (i = 1; i < DistCrv2 -> Length; i++) {
	Dist = (PType == CAGD_PT_E1_TYPE ? PtsX[i]
					 : (PtsX[i] / PtsW[i])) - Eps2;
	CloseEnough = Dist < 0.0 && MinSubdivLevel <= Level;

	if (CloseEnough != LastCloseEnough ||
	    (i == DistCrv2 -> Length - 1 && !LastCloseEnough)) {
	    CagdRType t;

	    if (CloseEnough == LastCloseEnough)
		t = Nodes[DistCrv2 -> Length - 1];
	    else
		CONVEX_BLEND(Nodes[i - 1], Nodes[i], LastDist, Dist, t);

	    if (CloseEnough ||
		(i == DistCrv2 -> Length - 1 && !LastCloseEnough)) {
		/* We are at the end of a region that is not close enough. */
		if (SinglePath) {
		    CagdRType
		        MidParam1 = (Crv1Param * 2.0 + Crv2Param) / 3.0,
			MidParam2 = (Crv1Param + Crv2Param * 2.0) / 3.0;
		    CagdCrvStruct
		        *AdapIso1, *AdapIso2, *AdapIso3, *AdapIso,
			*MidCrv1 = CagdCrvFromSrf(Srf, MidParam1, Dir),
			*MidCrv2 = CagdCrvFromSrf(Srf, MidParam2, Dir);

		    if (FullIso) {
			AdapIso1 = CagdAdapIsoExtractAux(Level + 1, Srf, NSrf,
							 Crv1, NULL,
							 MidCrv1, NULL,
							 Crv1Param, MidParam1,
							 Dir, Eps2,
							 FullIso, SinglePath);
			AdapIso2 = CagdAdapIsoExtractAux(Level + 1, Srf, NSrf,
							 MidCrv1, NULL,
							 MidCrv2, NULL,
							 MidParam1, MidParam2,
							 Dir, Eps2,
							 FullIso, SinglePath);
			AdapIso3 = CagdAdapIsoExtractAux(Level + 1, Srf, NSrf,
							 MidCrv2, NULL,
							 Crv2, NULL,
							 MidParam2, Crv2Param,
							 Dir, Eps2,
							 FullIso, SinglePath);

			if (AdapIso1 != NULL) {
			    for (TCrv = AdapIso1;
				 TCrv -> Pnext != NULL;
				 TCrv = TCrv -> Pnext);
			    TCrv -> Pnext = MidCrv1;
			    AdapIso = AdapIso1;
			}
			else
			    AdapIso = MidCrv1; 
			if (AdapIso2 != NULL)
			    MidCrv1 -> Pnext = AdapIso2;

			if (AdapIso2 != NULL) {
			    for (TCrv = AdapIso2;
				 TCrv -> Pnext != NULL;
				 TCrv = TCrv -> Pnext);
			    TCrv -> Pnext = MidCrv2;
			}
			else
			    MidCrv1 -> Pnext = MidCrv2;
			if (AdapIso2 != NULL)
			    MidCrv2 -> Pnext = AdapIso3;

			/* Chain these isolines to the entire set.*/
			if (AllAdapIso)
			    AllAdapIsoTail -> Pnext = AdapIso;
			else
			    AllAdapIso = AdapIso;

			for (AllAdapIsoTail = MidCrv2;
			     AllAdapIsoTail -> Pnext != NULL;
			     AllAdapIsoTail = AllAdapIsoTail -> Pnext);

			break; /* Only one (entire domain) region! */
		    }
		    else {
			CagdCrvStruct *Region1, *Region2,
			    *MidRegion1, *MidRegion2;

			Region1 = CagdCrvRegionFromCrv(Crv1, LastT, t),
			Region2 = CagdCrvRegionFromCrv(Crv2, LastT, t);
			MidRegion1 = CagdCrvRegionFromCrv(MidCrv1, LastT, t);
			MidRegion2 = CagdCrvRegionFromCrv(MidCrv2, LastT, t);
			CagdCrvFree(MidCrv1);
			CagdCrvFree(MidCrv2);

			AdapIso1 = CagdAdapIsoExtractAux(Level + 1, Srf, NSrf,
							 Region1, NULL,
							 MidRegion1, NULL,
							 Crv1Param, MidParam1,
							 Dir, Eps2,
							 FullIso, SinglePath);
			AdapIso2 = CagdAdapIsoExtractAux(Level + 1, Srf, NSrf,
							 MidRegion1, NULL,
							 MidRegion2, NULL,
							 MidParam1, MidParam2,
							 Dir, Eps2,
							 FullIso, SinglePath);
			AdapIso3 = CagdAdapIsoExtractAux(Level + 1, Srf, NSrf,
							 MidRegion2, NULL,
							 Region2, NULL,
							 MidParam2, Crv2Param,
							 Dir, Eps2,
							 FullIso, SinglePath);

			if (AdapIso1 != NULL) {
			    for (TCrv = AdapIso1;
				 TCrv -> Pnext != NULL;
				 TCrv = TCrv -> Pnext);
			    TCrv -> Pnext = MidRegion1;
			    AdapIso = AdapIso1;
			}
			else
			    AdapIso = MidRegion1; 
			if (AdapIso2 != NULL)
			    MidRegion1 -> Pnext = AdapIso2;

			if (AdapIso2 != NULL) {
			    for (TCrv = AdapIso2;
				 TCrv -> Pnext != NULL;
				 TCrv = TCrv -> Pnext);
			    TCrv -> Pnext = MidRegion2;
			}
			else
			    MidRegion1 -> Pnext = MidRegion2;
			if (AdapIso2 != NULL)
			    MidRegion2 -> Pnext = AdapIso3;

			/* Chain these isolines to the entire set.*/
			if (AllAdapIso)
			    AllAdapIsoTail -> Pnext = AdapIso;
			else
			    AllAdapIso = AdapIso;

			for (AllAdapIsoTail = MidRegion2;
			     AllAdapIsoTail -> Pnext != NULL;
			     AllAdapIsoTail = AllAdapIsoTail -> Pnext);
		    }
		}
		else { /* Return all the isocurves as a list of curves. */
		    CagdRType
		        MidParam = (Crv1Param + Crv2Param) / 2.0;
		    CagdCrvStruct *AdapIso1, *AdapIso2, *AdapIso,
			*MidCrv = CagdCrvFromSrf(Srf, MidParam, Dir),
			*MidNCrv = NSrf ? CagdCrvFromSrf(NSrf, MidParam, Dir)
					: NULL;

		    if (FullIso) {
			AdapIso1 = CagdAdapIsoExtractAux(Level + 1, Srf, NSrf,
							 Crv1, NCrv1,
							 MidCrv, MidNCrv,
							 Crv1Param, MidParam,
							 Dir, Eps2,
							 FullIso, SinglePath);
			AdapIso2 = CagdAdapIsoExtractAux(Level + 1, Srf, NSrf,
							 MidCrv, MidNCrv,
							 Crv2, NCrv2,
							 MidParam, Crv2Param,
							 Dir, Eps2,
							 FullIso, SinglePath);

			if (AdapIso1 != NULL) {
			    for (TCrv = AdapIso1;
				 TCrv -> Pnext != NULL;
				 TCrv = TCrv -> Pnext);
			    TCrv -> Pnext = MidCrv;
			    AdapIso = AdapIso1;
			}
			else
			    AdapIso = MidCrv;

			if (NSrf != NULL) {
			    MidCrv -> Pnext = MidNCrv;
			    MidCrv = MidNCrv;
			}

			if (AdapIso2 != NULL)
			    MidCrv -> Pnext = AdapIso2;

			/* Chain these isolines to the entire set.*/
			if (AllAdapIso)
			    AllAdapIsoTail -> Pnext = AdapIso;
			else
			    AllAdapIso = AdapIso;

			for (AllAdapIsoTail = MidCrv;
			     AllAdapIsoTail -> Pnext != NULL;
			     AllAdapIsoTail = AllAdapIsoTail -> Pnext);

			break; /* Only one (entire domain) region! */
		    }
		    else {
			CagdCrvStruct *Region1, *Region2, *NRegion1, *NRegion2,
			    *MidRegion, *MidNRegion;

			Region1 = CagdCrvRegionFromCrv(Crv1, LastT, t);
			Region2 = CagdCrvRegionFromCrv(Crv2, LastT, t);
			MidRegion = CagdCrvRegionFromCrv(MidCrv, LastT, t);
			if (NSrf) {
			    NRegion1 = CagdCrvRegionFromCrv(NCrv1, LastT, t);
			    NRegion2 = CagdCrvRegionFromCrv(NCrv2, LastT, t);
			    MidNRegion = CagdCrvRegionFromCrv(MidNCrv, LastT,
							      t);
			    CagdCrvFree(MidNCrv);
			}
			CagdCrvFree(MidCrv);

			AdapIso1 = CagdAdapIsoExtractAux(Level + 1, Srf, NSrf,
							 Region1, NRegion1,
							 MidRegion, MidNRegion,
							 Crv1Param, MidParam,
							 Dir, Eps2,
							 FullIso, SinglePath);
			AdapIso2 = CagdAdapIsoExtractAux(Level + 1, Srf, NSrf,
							 MidRegion, MidNRegion,
							 Region2, NRegion2,
							 MidParam, Crv2Param,
							 Dir, Eps2,
							 FullIso, SinglePath);

			if (AdapIso1 != NULL) {
			    for (TCrv = AdapIso1;
				 TCrv -> Pnext != NULL;
				 TCrv = TCrv -> Pnext);
			    TCrv -> Pnext = MidRegion;
			    AdapIso = AdapIso1;
			}
			else
			    AdapIso = MidRegion;

			if (NSrf != NULL) {
			    MidRegion -> Pnext = MidNRegion;
			    MidRegion = MidNRegion;
			}

			if (AdapIso2 != NULL)
			    MidRegion -> Pnext = AdapIso2;

			CagdCrvFree(Region1);
			CagdCrvFree(Region2);
			if (NSrf != NULL) {
			    CagdCrvFree(NRegion1);
			    CagdCrvFree(NRegion2);
			}

			/* Chain these isolines to the entire set.*/
			if (AllAdapIso)
			    AllAdapIsoTail -> Pnext = AdapIso;
			else
			    AllAdapIso = AdapIso;

			for (AllAdapIsoTail = MidRegion;
			     AllAdapIsoTail -> Pnext != NULL;
			     AllAdapIsoTail = AllAdapIsoTail -> Pnext);
		    }
		}
	    }
	    else {
		/* We are at the beginning of a region not close enough. */
		LastT = t;
	    }
	}

	LastDist = Dist;
	LastCloseEnough = CloseEnough;
    }

    CagdCrvFree(DistCrv2);
    IritFree((VoidPtr) Nodes);

    if (SinglePath) { /* Add connecting isocurves into the existing curves. */
	/* Not implemented yet. */
    }

    return AllAdapIso;
}
