/******************************************************************************
* CagdOfst.c - computes offset approximation to curves and surfaces.          *
*******************************************************************************
* Written by Gershon Elber, March. 93.					      *
******************************************************************************/

#include "cagd_loc.h"

#define MAX_OFFSET_IMPROVE_ITERS	20
#define NORMAL_PERTURB			1e-3
#define OFFSET_TRIM_EPS			1.25

/******************************************************************************
* Given a curve and an offset amount Offset, returns an approximation to the  *
* offset curve by offseting the control polygon in the normal direction.      *
******************************************************************************/
CagdCrvStruct *CagdCrvOffset(CagdCrvStruct *Crv, CagdRType OffsetDist)
{
    CagdBType
	IsNotRational = !CAGD_IS_RATIONAL_CRV(Crv);
    int i, j,
	MaxCoord = CAGD_NUM_OF_PT_COORD(Crv -> PType),
	Order = Crv -> Order,
	Length = Crv -> Length;
    CagdBType
	HasNewKV = FALSE;
    CagdVecStruct *N;
    CagdCrvStruct
	*OffsetCrv = CagdCrvCopy(Crv);
    CagdRType *Nodes, *NodePtr,
	**Points = OffsetCrv -> Points,
	*KV = NULL;

    switch (Crv -> GType) {
	case CAGD_CBEZIER_TYPE:
	    HasNewKV = TRUE;
	    KV = BspKnotUniformOpen(Length, Order, NULL);
	    break;
	case CAGD_CBSPLINE_TYPE:
	    KV = Crv -> KnotVector;
	    break;
	case CAGD_CPOWER_TYPE:
	    FATAL_ERROR(CAGD_ERR_POWER_NO_SUPPORT);
	    return NULL;
	default:
	    FATAL_ERROR(CAGD_ERR_UNDEF_CRV);
	    return NULL;
    }

    Nodes = BspKnotNodes(KV, Length + Order, Order);
    NodePtr = Nodes;

    if (IsNotRational)
	for (j = 0; j < Length; j++, NodePtr++) {
	    if ((N = CagdCrvNormal(Crv, *NodePtr)) == NULL &&
		(N = CagdCrvNormal(Crv, NodePtr == Nodes ?
				        *NodePtr + NORMAL_PERTURB :
				        *NodePtr - NORMAL_PERTURB)) == NULL) {
		FATAL_ERROR(CAGD_ERR_CANNOT_COMP_NORMAL);
		return NULL;
	    }
	    for (i = 1; i <= MaxCoord; i++)
		Points[i][j] += N -> Vec[i - 1] * OffsetDist;
	}
    else
	for (j = 0; j < Length; j++, NodePtr++) {
	    if ((N = CagdCrvNormal(Crv, *NodePtr)) == NULL &&
		(N = CagdCrvNormal(Crv, NodePtr == Nodes ?
				        *NodePtr + NORMAL_PERTURB :
				        *NodePtr - NORMAL_PERTURB)) == NULL) {
		FATAL_ERROR(CAGD_ERR_CANNOT_COMP_NORMAL);
		return NULL;
	    }
	    for (i = 1; i <= MaxCoord; i++)
		Points[i][j] = Points[i][j] +
			       N -> Vec[i - 1] * OffsetDist * Points[W][j];
	}

    if (HasNewKV)
	IritFree((VoidPtr) KV);
    IritFree((VoidPtr) Nodes);

    return OffsetCrv;
}

/******************************************************************************
* Given a surface and an offset amount Offset, returns an approximation to    *
* the offset surface by offseting the control mesh in the normal direction.   *
******************************************************************************/
CagdSrfStruct *CagdSrfOffset(CagdSrfStruct *Srf, CagdRType OffsetDist)
{
    CagdBType
	IsNotRational = !CAGD_IS_RATIONAL_SRF(Srf);
    int	i, Row, Col,
	MaxCoord = CAGD_NUM_OF_PT_COORD(Srf -> PType),
	UOrder = Srf -> UOrder,
	VOrder = Srf -> VOrder,
	ULength = Srf -> ULength,
	VLength = Srf -> VLength;
    CagdBType
	HasNewKV = FALSE;
    CagdVecStruct *N;
    CagdSrfStruct
	*OffsetSrf = CagdSrfCopy(Srf);
    CagdRType *UNodes, *VNodes, *UNodePtr, *VNodePtr,
	**Points = OffsetSrf -> Points,
	*UKV = NULL,
	*VKV = NULL;

    switch (Srf -> GType) {
	case CAGD_SBEZIER_TYPE:
	    HasNewKV = TRUE;
	    UKV = BspKnotUniformOpen(ULength, UOrder, NULL);
	    VKV = BspKnotUniformOpen(VLength, VOrder, NULL);
	    break;
	case CAGD_SBSPLINE_TYPE:
	    UKV = Srf -> UKnotVector;
	    VKV = Srf -> VKnotVector;
	    break;
	case CAGD_SPOWER_TYPE:
	    FATAL_ERROR(CAGD_ERR_POWER_NO_SUPPORT);
	    return NULL;
	default:
	    FATAL_ERROR(CAGD_ERR_UNDEF_CRV);
	    return NULL;
    }

    UNodes = BspKnotNodes(UKV, ULength + UOrder, UOrder);
    VNodes = BspKnotNodes(VKV, VLength + VOrder, VOrder);

    if (IsNotRational)
	for (Row = 0, VNodePtr = VNodes; Row < VLength; Row++, VNodePtr++)
	    for (Col = 0, UNodePtr = UNodes; Col < ULength; Col++, UNodePtr++) {
	    	N = CagdSrfNormal(Srf, *UNodePtr, *VNodePtr);
	    	for (i = 1; i <= MaxCoord; i++)
		    Points[i][CAGD_MESH_UV(OffsetSrf, Col, Row)] +=
			N -> Vec[i - 1] * OffsetDist;
	    }
    else
	for (Row = 0, VNodePtr = VNodes; Row < VLength; Row++, VNodePtr++)
	    for (Col = 0, UNodePtr = UNodes; Col < ULength; Col++, UNodePtr++) {
	    	N = CagdSrfNormal(Srf, *UNodePtr, *VNodePtr);
	    	for (i = 1; i <= MaxCoord; i++)
	    	    Points[i][CAGD_MESH_UV(OffsetSrf, Col, Row)] +=
			N -> Vec[i - 1] * OffsetDist *
			    Points[W][CAGD_MESH_UV(OffsetSrf, Col, Row)];
	    }

    if (HasNewKV) {
	IritFree((VoidPtr) UKV);
	IritFree((VoidPtr) VKV);
    }

    IritFree((VoidPtr) UNodes);
    IritFree((VoidPtr) VNodes);

    return OffsetSrf;
}

/******************************************************************************
* Given a curve and an offset amount OffsetDist, returns an approx. to the    *
* offset curve by offseting the control polygon in the normal direction.      *
*   This function computes an approximation to the offset using		      *
* OffsetAprxFunc, measure the error and use it to refine and decrease the     *
* error. Bezier curves are promoted to Bsplines. See paper below for more.    *
*									      *
* + Gershon Elber and Elaine Cohen. Error Bounded Variable Distance Offset    *
*   Operator for Free Form Curves and Surfaces. International Journal of      *
*   Computational Geometry & Applications, Vol. 1, Num. 1, March 1991,        *
*   pp 67-78.								      *
******************************************************************************/
CagdCrvStruct *CagdCrvAdapOffset(CagdCrvStruct *OrigCrv,
				 CagdRType OffsetDist,
				 CagdRType OffsetError,
				 CagdCrvFuncType OffsetAprxFunc)
{
    int i;
    CagdBType
	IsRational = CAGD_IS_RATIONAL_CRV(OrigCrv);
    CagdRType Min, Max, TMin, TMax,
	OffsetDistSqr = SQR(OffsetDist);
    CagdCrvStruct *OffsetCrv, *Crv;

    switch (OrigCrv -> GType) {
	case CAGD_CBEZIER_TYPE:
	    Crv = CnvrtBezier2BsplineCrv(OrigCrv);
	    break;
	case CAGD_CBSPLINE_TYPE:
	    Crv = CagdCrvCopy(OrigCrv);
	    break;
	case CAGD_CPOWER_TYPE:
	default:
	    FATAL_ERROR(CAGD_ERR_UNDEF_CRV);
	    Crv = NULL;
	    break;
    }

    if (OffsetAprxFunc == NULL)
	OffsetAprxFunc = CagdCrvOffset;

    CagdCrvDomain(Crv, &TMin, &TMax);

    for (i = 0; i < MAX_OFFSET_IMPROVE_ITERS; i++) {
	CagdCrvStruct *DiffCrv, *DistSqrCrv;

	OffsetCrv = OffsetAprxFunc(Crv, OffsetDist);
	DiffCrv = CagdCrvSub(OffsetCrv, Crv);
	DistSqrCrv = CagdCrvDotProd(DiffCrv, DiffCrv);
	CagdCrvFree(DiffCrv);

	CagdCrvMinMax(DistSqrCrv, 1, &Min, &Max);

	if (OffsetDistSqr - Min < OffsetError &&
	    Max - OffsetDistSqr < OffsetError) {
	    /* Error is within bounds - returns this offset approximation. */
	    CagdCrvFree(DistSqrCrv);
	    break;
	}
	else {
	    /* Refine in regions where the error is too large. */
	    int j, k,
	        Length = DistSqrCrv -> Length,
	        Order = DistSqrCrv -> Order,
	        KVLen = Length + Order;
	    CagdRType
		*KV = DistSqrCrv -> KnotVector,
		*Nodes = BspKnotNodes(KV, KVLen, Order),
		*RefKV = (CagdRType *) IritMalloc(sizeof(CagdRType) * 2 * Length);

	    for (j = k = 0; j < Length; j++) {
		CagdRType
		    *Pt = CagdCrvEval(DistSqrCrv, Nodes[j]),
		    V = OffsetDistSqr - (IsRational ? Pt[1] / Pt[0] : Pt[1]);

		if (ABS(V) > OffsetError) {
		    int Index = BspKnotLastIndexLE(KV, KVLen, Nodes[j]);

		    if (APX_EQ(KV[Index], Nodes[j])) {
			if (j > 0)
			    RefKV[k++] = (Nodes[j] + Nodes[j - 1]) / 2.0;
			if (j < Length - 1)
			    RefKV[k++] = (Nodes[j] + Nodes[j + 1]) / 2.0;
		    }
		    else
			RefKV[k++] = Nodes[j];
		}
	    }
	    CagdCrvFree(DistSqrCrv);
	    IritFree((VoidPtr) Nodes);

	    if (k == 0) {
		/* No refinement was found needed - return current curve. */
		IritFree((VoidPtr) RefKV);
		break;
	    }
	    else {
		CagdCrvStruct
		    *CTmp = CagdCrvRefineAtParams(Crv, FALSE, RefKV, k);

		IritFree((VoidPtr) RefKV);
		CagdCrvFree(Crv);
		Crv = CTmp;
	    }
	}
    }

    CagdCrvFree(Crv);
    return OffsetCrv;
}

/******************************************************************************
* Same as CagdCrvAdapOffset, but trims the self intersection loops.	      *
* See paper below for more.						      *
*									      *
* + Gershon Elber and Elaine Cohen. Error Bounded Variable Distance Offset    *
*   Operator for Free Form Curves and Surfaces. International Journal of      *
*   Computational Geometry & Applications, Vol. 1, Num. 1, March 1991,        *
*   pp 67-78.								      *
******************************************************************************/
CagdCrvStruct *CagdCrvAdapOffsetTrim(CagdCrvStruct *OrigCrv,
				     CagdRType OffsetDist,
				     CagdRType OffsetError,
				     CagdCrvFuncType OffsetAprxFunc)
{
    CagdBType
        IsRational = CAGD_IS_RATIONAL_CRV(OrigCrv);
    CagdCrvStruct *CrvtrSqr, *CrvtrSign, *Crv, *Crvs, *LastCrv,
	*CrvOffsetList = NULL;
    CagdPtStruct *Pts, *PtsHead, *LastPts;
    CagdRType
        OffsetDistSqr1 = 1.0 / SQR(OffsetDist);

    switch (OrigCrv -> GType) {
	case CAGD_CBEZIER_TYPE:
	    Crv = CnvrtBezier2BsplineCrv(OrigCrv);
	    break;
	case CAGD_CBSPLINE_TYPE:
	    Crv = CagdCrvCopy(OrigCrv);
	    break;
	default:
	    FATAL_ERROR(CAGD_ERR_UNDEF_CRV);
	    break;
    }

    if (OffsetDist == 0.0)
	return Crv;

    CrvtrSqr = CagdCrvCurvatureSqr(Crv);
    CrvtrSign = CagdCrvCurvatureSign(Crv);

    PtsHead = CagdCrvConstSet(CrvtrSqr, 1, SQR(OffsetError),
			      OffsetDistSqr1 / SQR(OFFSET_TRIM_EPS));

    for (Pts = PtsHead, LastPts = NULL; Pts != NULL;) {
	CagdRType *CrvtrSignPt, CrvtrSignVal;

	CrvtrSignPt = CagdCrvEval(CrvtrSign, Pts -> Pt[0]);
	CrvtrSignVal = IsRational ? CrvtrSignPt[1] / CrvtrSignPt[0]
				  : CrvtrSignPt[1];

	if (CrvtrSignVal * OffsetDist > 0.0) {
	    /* Remove point from list. */
	    if (LastPts != NULL) {
		LastPts -> Pnext = Pts -> Pnext;
		IritFree((VoidPtr) Pts);
		Pts = LastPts -> Pnext;
	    }
	    else {
		PtsHead = PtsHead -> Pnext;
		IritFree((VoidPtr) Pts);
		Pts = PtsHead;
	    }
	}
	else {
	    LastPts = Pts;
	    Pts = Pts -> Pnext;
	}
    }

    for (Pts = PtsHead;
	 Pts != NULL || Crv != NULL;
	 Pts = (Pts != NULL ? Pts -> Pnext : NULL)) {
	CagdCrvStruct
	    *Crv1 = Pts ? CagdCrvSubdivAtParam(Crv, Pts -> Pt[0])
			: CagdCrvCopy(Crv),
	    *Crv2 = Pts ? Crv1 -> Pnext : NULL;
	CagdRType Min, Max, *CrvtrSqrPt, CrvtrSqrVal,
						*CrvtrSignPt, CrvtrSignVal;

	CagdCrvDomain(Crv1, &Min, &Max);

	CrvtrSqrPt = CagdCrvEval(CrvtrSqr, (Min + Max) / 2);
	CrvtrSqrVal = CrvtrSqrPt[1] / CrvtrSqrPt[0];
	CrvtrSignPt = CagdCrvEval(CrvtrSign, (Min + Max) / 2);
	CrvtrSignVal = IsRational ? CrvtrSignPt[1] / CrvtrSignPt[0]
				  : CrvtrSignPt[1];

	if (CrvtrSqrVal < OffsetDistSqr1 / SQR(OFFSET_TRIM_EPS) ||
	    CrvtrSignVal * OffsetDist > 0.0) {
	    CagdCrvStruct
		*Crv1Off = CagdCrvAdapOffset(Crv1, OffsetDist, OffsetError,
					     OffsetAprxFunc);

	    CAGD_LIST_PUSH(Crv1Off, CrvOffsetList);
	}

	CagdCrvFree(Crv1);
	CagdCrvFree(Crv);
	Crv = Crv2;
    }

    Crvs = CagdListReverse(CrvOffsetList);
    CrvOffsetList = NULL;
    LastCrv = Crvs;
    Crvs = Crvs -> Pnext;
    for (Crv = Crvs; Crv != NULL; Crv = Crv -> Pnext) {
	CagdCrvStruct *CTmp, *CTmp2;
	CagdSrfStruct
	    *DistSrf = CagdSrfDistCrvCrv(LastCrv, Crv);
	CagdPtStruct
	    *IPts = CagdSrfDistFindPoints(DistSrf, OffsetError, FALSE);

	CagdSrfFree(DistSrf);

	if (IPts != NULL) {
	    if (IPts -> Pnext) {
		FATAL_ERROR(CAGD_ERR_TOO_COMPLEX);
		return NULL;
	    }
	    else {
		CagdRType TMin, TMax;

		CagdCrvDomain(LastCrv, &TMin, &TMax);
		CTmp = CagdCrvRegionFromCrv(LastCrv, TMin, IPts -> Pt[0]);
		CagdCrvFree(LastCrv);
		LastCrv = CTmp;

		CagdCrvDomain(Crv, &TMin, &TMax);
		CTmp = CagdCrvRegionFromCrv(Crv, IPts -> Pt[1], TMax);

		CTmp2 = CagdMergeCrvCrv(LastCrv, CTmp);
		CagdCrvFree(CTmp);
		CagdCrvFree(LastCrv);
		LastCrv = CTmp2;
	    }
	}
	else {
	    /* Simply chain the pieces together. */
	    CTmp = CagdMergeCrvCrv(LastCrv, Crv);
	    CagdCrvFree(LastCrv);
	    LastCrv = CTmp;
	}
    }

    CagdPtFreeList(PtsHead);
    CagdCrvFree(CrvtrSqr);
    CagdCrvFree(CrvtrSign);

    return LastCrv;
}
