//================================================================================================
// EQUINOX-3D(TM) plugin
//
// - Duplicate with repeat
//
// E3dTODO:
// - Implement _Copy() method
// E3dBUG
// - Brings hierarchy "back from the dead" (undo buffer): When the input changes, deleted result hierarchies come back.
//
// AUTHOR:  Gabor Nagy
// DATE:    2026-Apr-16 02:03:46
//
// Copyright (C) 2014-2026 by Gabor Nagy. All rights reserved.
//================================================================================================
#include <assert.h>
#include <math.h>
#include <stdlib.h>
#include <stdio.h>

#include <ELists.h>
#include <EMalloc.h>
#include <EPlugins.h>

#include <Xe/Matrix.h>
#include <Xe/Scale.h>
#include <Xe/Separator.h>
#include <Xe/PushButton.h>
#include <Xe/ToggleButton.h>

#include <E3D/Matrix.h>
#include <E3D/Select.h>

#include <E3D/UI/Panel.h>
#include <E3D/UI/Panels.h>
#include <E3D/UI/StatusPanel.h>  // For E3dp_PrintMessage
#include <E3D/UI/TimePanel.h>
#include <E3D/UI/WidgetCombos.h>

#include "Duplicate.h"

EpIconDefI(static, DuplicateBig);


static XeDWidget      _MenuButtons[8];static int  _MenuButtons_Count = 0;


static const char  _AnimationSemantic_DuplicateOverShape[] = "DuplicateOverShape";

static const E3dCoordinate  _SplineTEpsilon = 0.0001;

enum
{
	E3dINCREMENTAL = 1,
	E3dOVER_SHAPE
};


enum
{
	E3dSCALESPL_X = 0,
	E3dSCALESPL_Y = 1,
	E3dSCALESPL_U = 2
};

enum
{
	CR_DUPLICATE_WITH_REPEAT = XeCR_CUSTOM0,
	CR_DUPLICATE_WITH_REPEAT_OVER_SHAPE,
	CR_SET_DUPLICATES,
	CR_SET_INSTANCE,
};



typedef struct
{
	EList(, E3dNode*, ClonedBranches);
	EBool          Instance;
} E3dState;


// Duplicate with repeat
//
//------------------------------------------------
// "DuplicateWithRepeat" NodeComponent class
//------------------------------------------------
typedef struct
{
	E3dNodeComponentCore;

	EChangeFlags        ChangeRequestsCached;  // If Flags & EComponentRESPOND_TO_INPUT_CHANGE = 0, we cache requests coming in to _InputChanged here

	E3dNode*            Node;                  // Point back to the Node that has this NodeComponent (for _Update's sake)
	E3dNode*            NodeToDuplicate;

	int                 NumOfDuplicates;

	EBool               Instance;
	int                 CloneFlags;

	EBool               VisibleOnly;           // If TRUE, only copy / instance visible Objects
	EBool               MakeModelsVisible;     // Make the copies / new instances visible, even if the original was hidden.
	uint32_t            ObjectTypeFlags;       // OR-ed flags of E3dItemMESH etc. if not 0, only copy / instance the ones specified

	char                Mode;                  // E3dINCREMENTAL or E3dOVER_SHAPE

// Over shape
//
	E3dModel*           Model;                 // Model over which to duplicate
	E3dBaseObject*      Shape;                 // Mesh, Spline etc. over which to duplicate
	E3dPolyGroup*       PolyGroup;
	E3dCoordinate       Offset;                // Starting offset for clone-along Spline. This can be animated, so e.g. a bulldozer's tracks can roll.

	EBool               OrientAlongObject;

	E3dCoord3           PathNormal;uint8_t  PathNormalSource;  // For duplicate along Spline

	E3dScalingBySpline  ScaleUBySpline;
	E3dScalingBySpline  ScaleXBySpline;
	E3dScalingBySpline  ScaleYBySpline;

// Incremental
//
	E3dTransformSet     TransformIncrement;



	EInterface          InputInterface;        // This is added to the input Geometries as an output node

// Current state, so we can detect specific changes and not rebuild everything all the time
//
	E3dState            Current;
} E3dDuplicateWithRepeatInfo;


#include "PathNormalGUI.h"

typedef struct
{
	XeWidget  LockW,
	          InstanceW, CopyMaterialsW,
	          IncrementalW, OverShapeW, ShapePickW, ShapeShowW,
	          OrientAlongObjectW, VisibleOnlyW, MakeModelsVisibleW,
	          CamerasW, LightsW, PointSetsW, SplinesW, FacesW, MeshesW,
	          ScaleUBySplineMatrixW,
	          ScaleBySplineWs[3], ScaleBySplinePickWs[3], ScaleBySplineShowWs[3],  // U, X, Y
	          ScaleUBySplineNewW,
	          DuplicatesScaleW,
	          OffsetMatrixW, OffsetW,
	          Separator0W,
	          TranslationXW, TranslationYW, TranslationZW,
	          RotationXW, RotationYW, RotationZW,
	          ScalingXW, ScalingYW, ScalingZW;

	E3dPathNormalGUI  PathNormalGUI;  // For duplicate along Spline
} E3dResourcesGUI;


#define ROffset(field)  (void*)(EPtrOffset(E3dDuplicateWithRepeatInfo, field))
#define ROffsetI(field)  EPtrOffset(E3dDuplicateWithRepeatInfo, field)
static EResource  _DuplicateWithRepeatResources_a[]=
{
	{ "Flags",                  EResUINT16,              ROffset(Flags) },

	{ "NumOfDuplicates",        EResINT32,               ROffset(NumOfDuplicates) },
	{ "Mode",                   EResINT8,                ROffset(Mode) },
	{ "Instance",               EResBOOLEAN,             ROffset(Instance) },
	{ "NodeToDuplicate",        EResOBJECT,              ROffset(NodeToDuplicate) },

	{ "Cloned branches",        EResOBJECT|EResDYNAMIC,  ROffset(Current.ClonedBranches), .Ext.Count= ROffsetI(Current.ClonedBranches_Count) },

	{ "Scaling",                E3dCOORD3_TYPE,          ROffset(TransformIncrement.Scaling) },
	{ "Rotation",               E3dCOORD3_TYPE,          ROffset(TransformIncrement.EulerRotation.Angles) },
	{ "Translation",            E3dCOORD3_TYPE,          ROffset(TransformIncrement.Translation) },

	{ "Shape",                  EResOBJECT,              ROffset(Shape) },
	{ "ShapeModel",             EResOBJECT,              ROffset(Model) },
	{ "Offset" ,                E3dCOORDINATE_TYPE,      ROffset(Offset) },
	{ "Orient along object",    EResBOOLEAN,             ROffset(OrientAlongObject) },

	{ "Visbile only",           EResBOOLEAN,         ROffset(VisibleOnly) },
	{ "Make Models visbile",    EResBOOLEAN,         ROffset(MakeModelsVisible) },
	{ "Object types",           EResUINT32,          ROffset(ObjectTypeFlags) },

	{ "ScaleUBySpline",         EResBOOLEAN,         ROffset(ScaleUBySpline.Active) },
	{ "ScaleUBySpline Object",  EResOBJECT,          ROffset(ScaleUBySpline.OrigSpline) },
	{ "ScaleUBySpline Model",   EResOBJECT,          ROffset(ScaleUBySpline.Model) },

	{ "ScaleXBySpline",         EResBOOLEAN,         ROffset(ScaleXBySpline.Active) },
	{ "ScaleXBySpline Object",  EResOBJECT,          ROffset(ScaleXBySpline.OrigSpline) },
	{ "ScaleXBySpline Model",   EResOBJECT,          ROffset(ScaleXBySpline.Model) },

	{ "ScaleYBySpline",         EResBOOLEAN,         ROffset(ScaleYBySpline.Active) },
	{ "ScaleYBySpline Object",  EResOBJECT,          ROffset(ScaleYBySpline.OrigSpline) },
	{ "ScaleYBySpline Model",   EResOBJECT,          ROffset(ScaleYBySpline.Model) },

	{ "PathNormal source",      EResUINT8,           ROffset(PathNormalSource) },
	{ "PathNormal",             E3dCOORDINATE_TYPE,  ROffset(PathNormal),   .Ext.Count= 3 },
};
#undef ROffset
#undef ROffsetI
static EResourceList  _DuplicateWithRepeatResources = EListS_s(_DuplicateWithRepeatResources_a);


//================================================
// Duplicate Model with incremental transforms
//================================================
static EChangeFlags _ProcIncremental(E3dModel* PRootModel, E3dDuplicateWithRepeatInfo* PInfo,  E3dObjectPtrListA* PObjects)
{
	EChangeFlags      LChanged = 0;
	const int         LDuplicates = PInfo->NumOfDuplicates;
	const EBool       LInstance = PInfo->Instance;
	const int         LRotationOrder = PRootModel->TransformSet.EulerRotation.Order;
	E3dCoord3         LScaling = PRootModel->TransformSet.Scaling;
	E3dEulerRotation  LEulerRotation = PRootModel->TransformSet.EulerRotation;
	E3dCoord3         LTranslation = PRootModel->TransformSet.Translation;

	int  LI = 0;for(;LI < LDuplicates;LI++)
	{
		E3dNode*  LNewRootNode = PInfo->Current.ClonedBranches[LI];if(LNewRootNode)
		{
			E3dModel*  LNewRootModel = E_DynamicCast(LNewRootNode, E3dModel);if(LNewRootModel)
			{
				E3dTransformSet*  LTransformSet = &PInfo->TransformIncrement;

				LScaling.X *= LTransformSet->Scaling.X;
				LScaling.Y *= LTransformSet->Scaling.Y;
				LScaling.Z *= LTransformSet->Scaling.Z;

				LEulerRotation.Angles.X += LTransformSet->EulerRotation.Angles.X;
				LEulerRotation.Angles.Y += LTransformSet->EulerRotation.Angles.Y;
				LEulerRotation.Angles.Z += LTransformSet->EulerRotation.Angles.Z;

				LTranslation.X += LTransformSet->Translation.X;
				LTranslation.Y += LTransformSet->Translation.Y;
				LTranslation.Z += LTransformSet->Translation.Z;

				LNewRootModel->TransformSet.Scaling = LScaling;
				LNewRootModel->TransformSet.EulerRotation = LEulerRotation;LNewRootModel->TransformSet.EulerRotation.Order = LRotationOrder;
				LNewRootModel->TransformSet.Translation = LTranslation;

				if(!LInstance) E3dNodeHrc_CollectObjects(LNewRootNode, E3dItemALL,  PObjects);

				LChanged |= E3dNodeHrc_GetTransformChangeFlagsByObjects(LNewRootNode) | E3dChgHIERARCHY;
			}
		}
	}

	return(LChanged);
}


static EBool _SplineIsInvertedOnX(const E3dSpline* PSpline)
{
	if(PSpline && PSpline->CVs.Count > 0)	return(PSpline->CVs.Items[0].Position.X > PSpline->CVs.Items[PSpline->CVs.Count - 1].Position.X);
	else return(FALSE);
}

static E3dCoordinate _SplineModuloT(const E3dCoordinate PU)
{
	if(PU < 0.0) return(E3d_FMod(PU, 1.0) + 1.0);
	else
	{
		if(E3d_Abs(PU - (E3dCoordinate)1.0) < _SplineTEpsilon) return(1.0);
		else if(E3d_Abs(PU) < _SplineTEpsilon) return(0.0);
		return(E3d_FMod(PU, 1.0));
	}
}


//========================================
// Duplicate Model over a Spline
//========================================
static EChangeFlags _ProcAlongSpline(E3dModel* PRootModel, E3dDuplicateWithRepeatInfo* PInfo, E3dObjectPtrListA* PObjects)
{
	EChangeFlags         LChanged = 0;
	const int            LDuplicates = PInfo->NumOfDuplicates;
	const EBool          LInstance = PInfo->Instance;
	const int            LRotationOrder = PRootModel->TransformSet.EulerRotation.Order;
	E3dModel*            LPathModel = (E3dModel*)(PInfo->Model);
	E3dSpline*           LPathSpline = (E3dSpline*)(PInfo->Shape);

//E3dCoord3	           LScaling = PRootModel->TransformSet.Scaling;
//E3dRotation          LRotation = PRootModel->TransformSet.Rotation;
	E3dCoord3            LTranslation = PRootModel->TransformSet.Translation;

	const EIndex         LUPoints = LDuplicates;
	E3dCoordinate*       LUs;ENewN(LUPoints, LUs);

	{
//		const E3dCoordinate  LUStep = LDuplicates > 1 ? 1.0 / (E3dCoordinate)(LDuplicates - 1) : 1.0;
		const E3dCoordinate  LUStep = LPathSpline->Closed ? 1.0 / (E3dCoordinate)LDuplicates : 1.0 / (E3dCoordinate)(LDuplicates - 1);

		if(PInfo->ScaleUBySpline.Active)
		{
			E3dCoordinate  LScaleUBySplineMinX = 0.0, LScaleUBySplineRangeX = 0.0;
			E3dSpline*     LScaleUBySpline = PInfo->ScaleUBySpline.OrigSpline;if(LScaleUBySpline && LScaleUBySpline->CVs.Count > 0)
			{
				E3dBBox  LBBox;if(E3dGeometry_GetBoundingBox((E3dGeometry*)LScaleUBySpline, E3dBB_ALL,  &LBBox))
				{
					LScaleUBySplineMinX = LBBox.Min.X;
					LScaleUBySplineRangeX = LBBox.Max.X - LBBox.Min.X;
				}

				const EBool  LInvertedSpline = _SplineIsInvertedOnX(LScaleUBySpline);

//printf("%s() %s:%d :", __FUNCTION__, __FILE__, __LINE__);fflush(stdout);
				EIndex  LUC = 0;E3dCoordinate  LU = 0.0;for(;LUC < LDuplicates;LUC++, LU += LUStep)
				{
					const E3dCoordinate  LVal = LInvertedSpline ? 1.0 - (LU * LScaleUBySplineRangeX + LScaleUBySplineMinX) : LU * LScaleUBySplineRangeX + LScaleUBySplineMinX;
					E3dCoordinate  LTU;E3dSpline_GetYValueFromX(LScaleUBySpline, LVal, &LTU);
					LUs[LUC] = _SplineModuloT(LTU);
//printf(" [% 7.2f % 7.2f % 7.2f]", LU, LVal, LTU);fflush(stdout);
				}
//printf("\n");fflush(stdout);
			}
		}
		else
		{
printf("%s() %s:%d  %sUs%s\n", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, ECodeNormal);fflush(stdout);
			int  LUC = 0;E3dCoordinate  LU = PInfo->Offset;for(;LUC < LDuplicates;LUC++, LU += LUStep)
			{
				LUs[LUC] = _SplineModuloT(LU);
printf(" %f -> %f", LU, LUs[LUC]);fflush(stdout);
			}
printf("\n");fflush(stdout);
		}
	}

	E3dSpline*   LScaleXBySpline = PInfo->ScaleXBySpline.Active ? PInfo->ScaleXBySpline.OrigSpline : NULL;const EBool  LScaleXBySplineInverted = _SplineIsInvertedOnX(PInfo->ScaleXBySpline.OrigSpline);
	E3dSpline*   LScaleYBySpline = PInfo->ScaleYBySpline.Active ? PInfo->ScaleYBySpline.OrigSpline : NULL;const EBool  LScaleYBySplineInverted = _SplineIsInvertedOnX(PInfo->ScaleYBySpline.OrigSpline);
	const EBool  LVaryScaling = LScaleXBySpline || LScaleYBySpline;


	E3dCoordinate  LScaleXBySplineMinX = 0.0, LScaleXBySplineRangeX = 0.0;
	if(LScaleXBySpline)
	{
		E3dBBox  LBBox;if(E3dGeometry_GetBoundingBox((E3dGeometry*)LScaleXBySpline, E3dBB_ALL,  &LBBox))
		{
			LScaleXBySplineMinX = LBBox.Min.X;
			LScaleXBySplineRangeX = LBBox.Max.X - LBBox.Min.X;
		}
	}

	E3dCoordinate  LScaleYBySplineMinX = 0.0, LScaleYBySplineRangeX = 0.0;
	if(LScaleYBySpline)
	{
		E3dBBox  LBBox;if(E3dGeometry_GetBoundingBox((E3dGeometry*)LScaleYBySpline, E3dBB_ALL,  &LBBox))
		{
			LScaleYBySplineMinX = LBBox.Min.X;
			LScaleYBySplineRangeX = LBBox.Max.X - LBBox.Min.X;
		}
	}


	if(PInfo->OrientAlongObject)
	{
//printf("%s() %s:%d  %sOrientAlongObject%s\n", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, ECodeNormal);fflush(stdout);
		E3dCoordinate*  LMatrices = NULL;EIndex  LMatrices_CountAllocated = 0;

		E3dSpline_GetTransforms(LPathSpline, NULL,  LUs, LUPoints,  PInfo->PathNormalSource, &PInfo->PathNormal,  &LMatrices, &LMatrices_CountAllocated,  NULL, NULL, NULL,  NULL);

		{
			const E3dCoordinate*  LSMatrix = LMatrices;
			EIndex  LUC = 0;for(;LUC < LDuplicates;LUC++, LSMatrix += E3dELEMENTS_IN_MATRIX)
			{
				E3dNode*  LNewRootNode = PInfo->Current.ClonedBranches[LUC];if(LNewRootNode)
				{
					E3dModel*  LNewRootModel = E_DynamicCast(LNewRootNode, E3dModel);if(LNewRootModel)
					{
						E3dMatrix  LMatrix;E3dMatrix_Copy(LSMatrix, LMatrix);

						LNewRootModel->TransformSet.EulerRotation.Order = LRotationOrder;

						if(LVaryScaling)
						{
							E3dCoordinate  LScalingX = 1.0;
							if(LScaleXBySpline)
							{
								const E3dCoordinate  LV = LScaleXBySplineInverted ? 1.0 - (LUs[LUC] * LScaleXBySplineRangeX + LScaleXBySplineMinX) : LUs[LUC] * LScaleXBySplineRangeX + LScaleXBySplineMinX;
								E3dSpline_GetYValueFromX(LScaleXBySpline, LV, &LScalingX);
							}

							E3dCoordinate  LScalingY = 1.0;
							if(LScaleYBySpline)
							{
								const E3dCoordinate  LV = LScaleYBySplineInverted ? 1.0 - (LUs[LUC] * LScaleYBySplineRangeX + LScaleYBySplineMinX) : LUs[LUC] * LScaleYBySplineRangeX + LScaleYBySplineMinX;
								E3dSpline_GetYValueFromX(LScaleYBySpline, LV, &LScalingY);
							}

							E3dMatrix  LScaleMatrix;
							LScaleMatrix[M00] = LScalingX;LScaleMatrix[M10] = 0.0;      LScaleMatrix[M20] = 0.0;LScaleMatrix[M30] = 0.0;
							LScaleMatrix[M01] = 0.0;      LScaleMatrix[M11] = LScalingY;LScaleMatrix[M21] = 0.0;LScaleMatrix[M31] = 0.0;
							LScaleMatrix[M02] = 0.0;      LScaleMatrix[M12] = 0.0;      LScaleMatrix[M22] = 1.0;LScaleMatrix[M32] = 0.0;
							LScaleMatrix[M03] = 0.0;      LScaleMatrix[M13] = 0.0;      LScaleMatrix[M23] = 0.0;LScaleMatrix[M33] = 1.0;

							E3dMatrix_Mult4x4_3(LScaleMatrix, LMatrix, LMatrix);

printf(" [%5.3f %5.3f %5.3f]", LUs[LUC], LScalingX, LScalingY);fflush(stdout);
//printf(" [%7.3f]", LUs[LUC]);fflush(stdout);
							if(LPathModel)
							{
//								E3dMatrix_Mult4x4(LPathModel->LocalToWorldMatrix, LMatrix);
							}
							E3dMatrix_DecomposeToScaleRotateTranslate(LMatrix, NULL, LNewRootModel->TransformSet.EulerRotation.Order, &LNewRootModel->TransformSet);
						}
						else
						{
							if(LPathModel)
							{
//								E3dMatrix_Mult4x4(LPathModel->LocalToWorldMatrix, LMatrix);
							}

//E3dMatrix_Print4x4(LMatrix);
							E3dMatrix_DecomposeToRotateTranslate(LMatrix, NULL, LNewRootModel->TransformSet.EulerRotation.Order, &LNewRootModel->TransformSet);
						}

						if(!LInstance) E3dNodeHrc_CollectObjects(LNewRootNode, E3dItemALL,  PObjects);

						LChanged |= E3dNodeHrc_GetTransformChangeFlagsByObjects(LNewRootNode) | E3dChgHIERARCHY;
					}
				}
			}
printf("\n");fflush(stdout);
		}


		EFree(LMatrices);
	}
	else
	{
// Don't orient the clones along the Spline
//
		E3dModel*  LModelToDuplicate = E_DynamicCast(PInfo->NodeToDuplicate, E3dModel);

//printf("%s() %s:%d  LModelToDuplicate [%s%p%s:%s%s%s]\n", __FUNCTION__, __FILE__, __LINE__,  ECodeCyan, LModelToDuplicate, ECodeNormal,  ECodeGreen, LModelToDuplicate && LModelToDuplicate->Name ? LModelToDuplicate->Name : "", ECodeNormal);fflush(stdout);
		int  LUC = 0;for(;LUC < LDuplicates;LUC++)
		{
			const E3dCoordinate  LU = LUs[LUC];
			E3dNode*  LNewRootNode = PInfo->Current.ClonedBranches[LUC];if(LNewRootNode)
			{
				E3dModel*  LNewRootModel = E_DynamicCast(LNewRootNode, E3dModel);if(LNewRootModel)
				{
					E3dSpline_Get3DPosition(LPathSpline, LU,  &LTranslation, NULL, NULL);

					E3dMatrix  LMatrix;E3dMatrix_LoadIdentity(LMatrix);

					if(LPathModel)
					{
//					E3dMatrix_Mult4x4(LPathModel->LocalToWorldMatrix, LMatrix);
					}

					E3dMatrix_Translate(LMatrix,  LTranslation.X, LTranslation.Y, LTranslation.Z);

					if(LVaryScaling)
					{
						E3dCoordinate  LScalingX = 1.0;
						if(LScaleXBySpline)
						{
							const E3dCoordinate  LV = LScaleXBySplineInverted ? 1.0 - (LUs[LUC] * LScaleXBySplineRangeX + LScaleXBySplineMinX) : LUs[LUC] * LScaleXBySplineRangeX + LScaleXBySplineMinX;
							E3dSpline_GetYValueFromX(LScaleXBySpline, LV, &LScalingX);
						}

						E3dCoordinate  LScalingY = 1.0;
						if(LScaleYBySpline)
						{
							const E3dCoordinate  LV = LScaleYBySplineInverted ? 1.0 - (LUs[LUC] * LScaleYBySplineRangeX + LScaleYBySplineMinX) : LUs[LUC] * LScaleYBySplineRangeX + LScaleYBySplineMinX;
							E3dSpline_GetYValueFromX(LScaleYBySpline, LV, &LScalingY);
						}

						E3dMatrix  LScaleMatrix;
						LScaleMatrix[M00] = LScalingX;LScaleMatrix[M10] = 0.0;      LScaleMatrix[M20] = 0.0;LScaleMatrix[M30] = 0.0;
						LScaleMatrix[M01] = 0.0;      LScaleMatrix[M11] = LScalingY;LScaleMatrix[M21] = 0.0;LScaleMatrix[M31] = 0.0;
						LScaleMatrix[M02] = 0.0;      LScaleMatrix[M12] = 0.0;      LScaleMatrix[M22] = 1.0;LScaleMatrix[M32] = 0.0;
						LScaleMatrix[M03] = 0.0;      LScaleMatrix[M13] = 0.0;      LScaleMatrix[M23] = 0.0;LScaleMatrix[M33] = 1.0;

						E3dMatrix_Mult4x4_3(LScaleMatrix, LMatrix, LMatrix);
						E3dMatrix_DecomposeToScaleRotateTranslate(LMatrix, NULL, LNewRootModel->TransformSet.EulerRotation.Order, &LNewRootModel->TransformSet);
					}
					else
					{
						E3dMatrix_DecomposeToRotateTranslate(LMatrix, NULL, LNewRootModel->TransformSet.EulerRotation.Order, &LNewRootModel->TransformSet);
					}

// Preserve the rotation of the original Model
//
					if(LModelToDuplicate) LNewRootModel->TransformSet.EulerRotation = PRootModel->TransformSet.EulerRotation;
					else LNewRootModel->TransformSet.EulerRotation.Order = LRotationOrder;

					if(!LInstance) E3dNodeHrc_CollectObjects(LNewRootNode, E3dItemALL,  PObjects);

					LChanged |= E3dNodeHrc_GetTransformChangeFlagsByObjects(LNewRootNode) | E3dChgHIERARCHY;
				}
			}
		}
	}

	EFree(LUs);

	return(LChanged);
}


//================================================
// Do duplication for DuplicateWithRepeat
//================================================
static void _DuplicateRep(E3dScene* PScene, E3dNode* PRootNodeToDuplicate, E3dNode* PUberRootNode, E3dDuplicateWithRepeatInfo* PInfo)
{
	E3dModel*  LRootModel = E_DynamicCast(PRootNodeToDuplicate, E3dModel);

	if(LRootModel && PUberRootNode)
	{
		E3dNode*            LHrcRoot = PUberRootNode;while(LHrcRoot->Parent) LHrcRoot = LHrcRoot->Parent;
		const int           LDuplicates = PInfo->NumOfDuplicates;
		const EBool         LInstance = PInfo->Instance;
		const int           LFlags = PInfo->CloneFlags;
		E3dObjectPtrListA   LObjects;EList_InitA_s(LObjects);

		char*               LNewRootName = PRootNodeToDuplicate->Name ? EMalloc(strlen(PRootNodeToDuplicate->Name) + 12) : EMalloc(12);

		EBool               LHrcChanged = FALSE;

// Free existing cloned branches that we won't use
//
		const EIndex  LCurrentClonedBranches_Count = PInfo->Current.ClonedBranches_Count;

printf("%s() %s:%d  [%p:%s]  LCurrentClonedBranches_Count %lld\n", __FUNCTION__, __FILE__, __LINE__, PUberRootNode, PUberRootNode->Name, (long long)LCurrentClonedBranches_Count);fflush(stdout);
		if(LCurrentClonedBranches_Count > LDuplicates)
		{
			EIndex  LBI = LDuplicates;for(;LBI < LCurrentClonedBranches_Count;LBI++)
			{
				E3dNode*  LClonedBranch = PInfo->Current.ClonedBranches[LBI];if(LClonedBranch)
				{
printf("%s() removing branch: [%s] %p RefCnt: %d\n", __FUNCTION__, LClonedBranch->Name, LClonedBranch, LClonedBranch->RefCnt);fflush(stdout);
					E3dNode_DisconnectFromParent(LClonedBranch, FALSE);
					E_Delete(LClonedBranch);
					LHrcChanged = TRUE;
				}
			}
			PInfo->Current.ClonedBranches_Count = LDuplicates;
		}

		PInfo->Current.Instance = PInfo->Instance;

// Add cloned branches, if needed
//
		if(LCurrentClonedBranches_Count < LDuplicates)
		{
			E3dNode*  LPrevSibling = PUberRootNode->Child;if(LPrevSibling) while(LPrevSibling->NextSibling) LPrevSibling = LPrevSibling->NextSibling;

			EIndex  LI = LCurrentClonedBranches_Count;for(;LI < LDuplicates;LI++)
			{
				E3dNode*  LNewRootNode = E3dNodeHrc_BranchCopy(PScene, PRootNodeToDuplicate, LFlags & ~(E3dCopyNODE_COMPONENTS), LInstance, PInfo->ObjectTypeFlags, PInfo->VisibleOnly, FALSE);if(LNewRootNode)
				{
					E3dModel*  LNewRootModel = E_DynamicCast(LNewRootNode, E3dModel);if(LNewRootModel)
					{
						sprintf(LNewRootName, "%s-%04lld", PRootNodeToDuplicate->Name, ((long long)LI + 1));
						EObject_SetName(LNewRootNode, EStrDup(LNewRootName), FALSE);
						EList_ObjectAdd(PInfo->Current.ClonedBranches, LNewRootNode);

// E3dNODE_LINKREF
						LNewRootNode->Parent = PUberRootNode;  // Node reference counting / tracking points from parent to child (->Parent is not refd), so we can just set this directly.
						if(LPrevSibling) E_AssignToMember(LNewRootNode, LPrevSibling,NextSibling);
						else E_AssignToMember(LNewRootNode, PUberRootNode,Child);

						LPrevSibling = LNewRootNode;
						LHrcChanged = TRUE;
					}
				}
			}
		}

		if(LHrcChanged) E3dNodeHrc_UpdateConnections(LHrcRoot);

		if(PInfo->MakeModelsVisible)
		{
			EIndex  LBRC = 0;const EIndex  LBRN = PInfo->Current.ClonedBranches_Count;for(;LBRC < LBRN;LBRC++)
			{
				E3dNode*  LNode = PInfo->Current.ClonedBranches[LBRC];
				E3dNode*  LNodeAfterBranch = E3dNode_BranchGetNodeAfter(LNode);
				for(;LNode != LNodeAfterBranch;LNode = LNode->Next) LNode->Visible = TRUE;
			}
		}



		EChangeFlags  LChanged = 0;

		switch(PInfo->Mode)
		{
			case E3dINCREMENTAL:
				LChanged |= _ProcIncremental(LRootModel, PInfo, &LObjects);
			break;

			case E3dOVER_SHAPE:
				if(PInfo->Shape)
				{
					if(EObject_IsSubClass(PInfo->Shape, E3dSpline))
					{
						LChanged |= _ProcAlongSpline(LRootModel, PInfo, &LObjects);
					}
/*
					else if(EObject_IsSubClass(PInfo->Shape, E3dMesh))
					{
						LChanged |= _ProcOverMesh(LRootModel, PInfo, &LObjects);
					}
*/
				}
			break;
		}


		if(LChanged)
		{
			if(PUberRootNode->Parent == NULL)
			{
				if(PRootNodeToDuplicate->Parent)
				{
					E3dNode_ConnectChild(PRootNodeToDuplicate->Parent, PUberRootNode, ELstEND, FALSE, TRUE);
				}
				else
				{
					E3dNodeHrc_UpdateMatrices(PUberRootNode, TRUE);
					E3dNodeHrc_UpdateMaterialInheritance(PUberRootNode);
					E3dScene_NodeHrcAdd(PScene, PUberRootNode, 0);
				}
			}
			else
			{
				E3dNodeHrc_UpdateMatrices(PUberRootNode, TRUE);
				E3dNodeHrc_UpdateMaterialInheritance(PUberRootNode);
			}


// Clone Node info after the Matrices had been updated (e.g. rigid body physics needs up-to-date Matrices)
//
			if(PInfo->Current.ClonedBranches)
			{
				EIndex  LI = 0;for(;LI < PInfo->Current.ClonedBranches_Count;LI++)
				{
					if(LFlags & E3dCopyCOMPONENTS) LChanged |= E3dNodeHrc_CloneInfo(PScene, PRootNodeToDuplicate, PInfo->Current.ClonedBranches[LI], E3dCopyNODE_COMPONENTS);
				}
			}

// Call UpdateProcs
//
			if(PInfo->Current.ClonedBranches)
			{
				E3dNodeHrc_CallNodeInfoUpdateProcs(PRootNodeToDuplicate, E3dSceneChgALL, FALSE);

				EIndex  LI = 0;for(;LI < PInfo->Current.ClonedBranches_Count;LI++)
				{
					E3dNodeHrc_CallNodeInfoUpdateProcs(PInfo->Current.ClonedBranches[LI], E3dSceneChgALL, FALSE);
				}
			}
		}

		if(LObjects.Items)
		{
			EIndex  LI = 0;for(;LI < LObjects.Count;LI++)
			{
				E3dObject*       LObject = LObjects.Items[LI];
				E3dObjectClass*  LClass = LObject->Class;

				if(LClass->UpdateForDisplay) LClass->UpdateForDisplay(LObject, E3dUpdateALL & ~E3dUpdateREMOVE_STRIPS);
			}

			EList_FreeAChk_s(LObjects);
		}

		EFree(LNewRootName);

		E3dScene_ObjectsUpdateModelList(PScene);
		E3dScene_LayoutOnSchematics(PScene, FALSE);

		E3dWindows_RedrawByChangeFlags(PScene, LChanged);
	}
}


//================================================================
// Callback for path Spline changes
// This function should Not call any 3DWindow redrawing functions,
// just return with the appropriate E3dCHG flags!
//================================================================
static EChangeFlags _InputChanged(EInterface* PInterface, EInterfaceCallbackStruct* PCBS)
{
	E3dDuplicateWithRepeatInfo*  LInfo = (E3dDuplicateWithRepeatInfo*)PInterface->ObjectComponent;
	EChangeFlags                 LChanged = 0;

	EIndex  LI = 0;const EObjectChg*  LObjectChg = PCBS->Objects;for(;LI < PCBS->Objects_Count;LI++, LObjectChg++)
	{
		EObject*    LEObject = LObjectChg->Object;
		E3dObject*  LE3dObject = E_DynamicCast(LEObject, E3dObject);

// E3dFIXME Only respond if the Model is instantiated (e.g. not in the undo stack after being deleted)
//
		if(LE3dObject && LE3dObject->Models.Count > 0)
		{
			if((E3dBaseObject*)LE3dObject == LInfo->Shape)
			{
				LChanged |= LObjectChg->Changed;
			}
			else if(LE3dObject == (E3dObject*)LInfo->ScaleUBySpline.OrigSpline)
			{
//printf("%s() %s:%d ScaleUBySpline\n", __FUNCTION__, __FILE__, __LINE__);fflush(stdout);
				LChanged |= LObjectChg->Changed;
			}
			else if(LE3dObject == (E3dObject*)LInfo->ScaleXBySpline.OrigSpline)
			{
//printf("%s() %s:%d ScaleUBySpline\n", __FUNCTION__, __FILE__, __LINE__);fflush(stdout);
				LChanged |= LObjectChg->Changed;
			}
			else if(LE3dObject == (E3dObject*)LInfo->ScaleYBySpline.OrigSpline)
			{
//printf("%s() %s:%d ScaleUBySpline\n", __FUNCTION__, __FILE__, __LINE__);fflush(stdout);
				LChanged |= LObjectChg->Changed;
			}
		}
//printf("%s() %s:%d  [%s] [%s]\n", __FUNCTION__, __FILE__, __LINE__, LInfo->NodeToDuplicate->Name, LInfo->Node->Name);fflush(stdout);
	}

	if(LChanged)
	{
{ char  s[8192];printf("%s() %s:%d  %08x %s\n", __FUNCTION__, __FILE__, __LINE__, LChanged, E3dSceneChangeFlags_ToString(LChanged, EfCOLOR,  s));fflush(stdout); }

		if(LInfo->Flags & EComponentRESPOND_TO_INPUT_CHANGE)
		{
{ char  s[8192];printf("%s() %s:%d  %08x %s\n", __FUNCTION__, __FILE__, __LINE__, LChanged, E3dSceneChangeFlags_ToString(LChanged, EfCOLOR,  s));fflush(stdout); }
			_DuplicateRep(E3d_Scene, LInfo->NodeToDuplicate, LInfo->Node, LInfo);

			const E3dNode*  LNode = LInfo->Node;const E3dNode*  LNodeAfterBranch = E3dNode_BranchGetNodeAfter(LNode);for(;LNode != LNodeAfterBranch;LNode = LNode->Next)
			{
				LChanged |= E3dModel_GetChangeFlagsByObjects((E3dModel*)LNode);
			}
			LChanged |= E3dChgHIERARCHY|E3dChgNodeTRANSFORM;
		}
		else
		{
			LInfo->ChangeRequestsCached |= LChanged;
			LChanged = 0;
		}
{ char  s[8192];printf("%s() %s:%d  %08x %s\n", __FUNCTION__, __FILE__, __LINE__, LChanged, E3dSceneChangeFlags_ToString(LChanged, EfCOLOR,  s));fflush(stdout); }
	}

	return(LChanged);
}


static E3dNodeComponentClass*  _DuplicateWithRepeatComponentClass = NULL;


static const char  _ScaleUToolTip[] = "Use the X and Y value of a Spline (like a 1D funtion) to adjust\nthe moving of the duplicated Model along the path Spline.";
static const char  _ScaleXToolTip[] = "Use the X and Y value of a Spline (like a 1D funtion) to vary\nthe X scaling of the duplicated Model along the path Spline.";
static const char  _ScaleYToolTip[] = "Use the X and Y value of a Spline (like a 1D funtion) to vary\nthe Y scaling of the duplicated Model along the path Spline.";


#define TmpCHANGEFLAGS  E3dChgMeshTRANSFORM|E3dChgSELECTION
#define E3dInfo  E3dDuplicateWithRepeatInfo
#define TmpComponentClass  _DuplicateWithRepeatComponentClass
#define TmpUpdateObject()    do { _DuplicateRep(E3d_Scene, LInfo->NodeToDuplicate, _TmpPanel->Node, LInfo);(void)LChanged; } while(0)
static E3dInfo*             _TmpInfo = NULL;
static E3dNodePanel*        _TmpPanel = NULL;
static E3dScalingBySpline*  _TmpScalingBySpline = NULL;
static int                  _TmpScalingBySplineIndex = -1;
	#include "ControlSpline.h"
#undef TmpComponentClass
#undef TmpUpdateObject
#undef TmpCHANGEFLAGS


#define _ScaleBySplineCBs(pCoordinate)\
static void _DCB_Scale##pCoordinate##BySpline(XeWidget PW, EPointer PClientData, EPointer PCallData)\
{\
	E3dNodePanel*  LPanel = (E3dNodePanel*)PClientData;\
	E3dNode*       LNode = LPanel->Node;\
	E3dInfo*       LInfo = (E3dInfo*)E3dNodeComponent_ByClass(LNode, _DuplicateWithRepeatComponentClass, FALSE);\
\
	LInfo->Scale##pCoordinate##BySpline.Active = ((XeToggleButtonCallbackStruct*)PCallData)->Set;\
\
	if(LInfo->Scale##pCoordinate##BySpline.Active && LInfo->Scale##pCoordinate##BySpline.OrigSpline == NULL)\
	{\
		_TmpScalingBySpline = &LInfo->Scale##pCoordinate##BySpline;\
		_TmpScalingBySplineIndex = E3dSCALESPL_##pCoordinate;\
		_PickSpline(": pick Spline to scale "#pCoordinate" by   ");\
	}\
	else _DuplicateRep(E3d_Scene, LInfo->NodeToDuplicate, LNode, LInfo);\
}\
\
static void _DCB_Scale##pCoordinate##BySplinePick(XeWidget PW, EPointer PClientData, EPointer PCallData)\
{\
	E3dNodePanel*  LPanel = (E3dNodePanel*)PClientData;\
	E3dNode*       LNode = LPanel->Node;\
	E3dInfo*       LInfo = (E3dInfo*)E3dNodeComponent_ByClass(LNode, _DuplicateWithRepeatComponentClass, FALSE);\
\
	_TmpScalingBySpline = &LInfo->Scale##pCoordinate##BySpline;\
	_TmpScalingBySplineIndex = E3dSCALESPL_##pCoordinate;\
	_PickSpline(": pick Spline to scale "#pCoordinate" by   ");\
}\
\
static void _DCB_Scale##pCoordinate##BySplineShow(XeWidget PW, EPointer PClientData, EPointer PCallData)\
{\
	E3dNodePanel*  LPanel = (E3dNodePanel*)PClientData;\
	E3dNode*       LNode = LPanel->Node;\
	E3dInfo*       LInfo = (E3dInfo*)E3dNodeComponent_ByClass(LNode, _DuplicateWithRepeatComponentClass, FALSE);\
	EChangeFlags   LChanged = 0;\
\
	E3dSpline*  LSpline = LInfo->Scale##pCoordinate##BySpline.OrigSpline;if(LSpline)\
	{\
		if(E3dSel_ObjectSelect(E3d_Scene, (E3dObject*)LSpline, FALSE, FALSE)) LChanged |= E3dChgSELECTION;\
	}\
\
	if(LChanged)\
	{\
		const E3dpPrefs*  LPrefs = &E3dp_Prefs;\
		E3dp_PrintCompoundMessageVa(ELogMESSAGE, 3000,  XeFONT, XeFontDEFAULT,  XeTEXT, "Selected ",  XeTEXT, #pCoordinate,  XeTEXT, " scaling Spline ",  E3dp_ObjectS(LSpline->Name ? LSpline->Name : "-"),  0);\
		E3dWindows_RedrawByChangeFlags(E3d_Scene, LChanged);\
		E3dTransformPanel_Update(NULL,  E3dDoALL, E3dDoALL, E3dDoALL,  E3dpDO_NOT_CHANGE,  E3dTrPanelALL);\
	}\
	else E3dp_PrintMessage(ELogWARNING, 5000, "No %s Scaling Spline found!", #pCoordinate);\
}


_ScaleBySplineCBs(U)
_ScaleBySplineCBs(X)
_ScaleBySplineCBs(Y)



//================================================
// E3dDuplicateWithRepeatInfo constructor
//================================================
static int _Init(E3dNode* PNode, E3dNodeComponent* PInfo)
{
	E3dDuplicateWithRepeatInfo*  LInfo = (E3dDuplicateWithRepeatInfo*)PInfo;

//printf("%s() %s:%d  %p\n", __FUNCTION__, __FILE__, __LINE__, LInfo);fflush(stdout);
	LInfo->ChangeRequestsCached = 0;
	LInfo->NodeToDuplicate = NULL;

	LInfo->Model = NULL;
	LInfo->Shape = NULL;
	LInfo->PolyGroup = NULL;
	LInfo->Offset = 0.0;

	LInfo->NumOfDuplicates = 3;
	LInfo->Instance = TRUE;
	LInfo->OrientAlongObject = TRUE;

	LInfo->VisibleOnly = FALSE;
	LInfo->MakeModelsVisible = FALSE;
	LInfo->ObjectTypeFlags = E3dItemALL;

	LInfo->Mode = E3dINCREMENTAL;

	LInfo->Flags |= EComponentANIMATABLE;  // E3dDuplicateWithRepeat has animateable fields (Offset...)

	E3d_ScalingBySplineInit(&LInfo->ScaleXBySpline);
	E3d_ScalingBySplineInit(&LInfo->ScaleYBySpline);
	E3d_ScalingBySplineInit(&LInfo->ScaleUBySpline);

	{
		E3dTransformSet*  LTransformSet = &LInfo->TransformIncrement;

		LTransformSet->Translation = E3dCoord3_s(1.0, 0.0, 0.0);
		E3dTransformSet_RotationInit(LTransformSet);LTransformSet->EulerRotation.Order = E3dZYX;
		LTransformSet->Scaling = E3dCoord3_s(1.0, 1.0, 1.0);
	}

	LInfo->CloneFlags = E3dCopyLODS|E3dCopyCOMPONENTS|E3dCopyANIMATIONS;
//LInfo->CloneFlags = E3dCopyLODS|E3dCopyCOMPONENTS|E3dCopyANIMATIONS|E3dCopyMATERIALS;

	EList_Init(LInfo->Current.ClonedBranches);
	LInfo->Current.Instance = LInfo->Instance;

	LInfo->PathNormalSource = E3dSplineNormalAUTO;LInfo->PathNormal = E3dCoord3_s(0.0, 0.0, 1.0);

// Initialize InputInterface for _InputChanged()
//
	{
		EInterface*  LInput = &LInfo->InputInterface;EInterface_Init(LInput);
		LInput->InputChanged = _InputChanged;
		LInput->Object = (ECoreObject*)PNode;
		LInput->ObjectComponent = (ECoreObject*)PInfo;
	}

	LInfo->Node = PNode;

	return(E_SUCCESS);
}


//================================================
// E3dDuplicateWithRepeatInfo destructor
//================================================
static int _Free(E3dNode* PNode, E3dNodeComponent* PInfo)
{
printf("%s() %s:%d  %p\n", __FUNCTION__, __FILE__, __LINE__, PInfo);fflush(stdout);
	E3dDuplicateWithRepeatInfo*  LInfo = (E3dDuplicateWithRepeatInfo*)PInfo;

	E_DeleteChk(LInfo->Model);
	E_DeleteChk(LInfo->Shape);
	E_DeleteChk(LInfo->PolyGroup);

	E_DeleteChk(LInfo->NodeToDuplicate);

	EList_ObjectsFree(LInfo->Current.ClonedBranches);

	E3d_ScalingBySplineFreeContents(&LInfo->ScaleUBySpline, &LInfo->InputInterface);
	E3d_ScalingBySplineFreeContents(&LInfo->ScaleXBySpline, &LInfo->InputInterface);
	E3d_ScalingBySplineFreeContents(&LInfo->ScaleYBySpline, &LInfo->InputInterface);

	EInterface_Clear(&LInfo->InputInterface);

	return(E_SUCCESS);
}


static void _SetGUI(E3dDuplicateWithRepeatInfo* PInfo, E3dResourcesGUI* PResourcesGUI)
{
	XeChangeManage   LChildren[16];
	XeChangeManage*  LChild = LChildren;
	const EBool      LIncremental = PInfo->Mode == E3dINCREMENTAL;
	const EBool      LOverShape = PInfo->Mode == E3dOVER_SHAPE;


	LChild->W = PResourcesGUI->Separator0W;LChild->Manage = LIncremental;LChild++;

	LChild->W = PResourcesGUI->TranslationXW;LChild->Manage = LIncremental;LChild++;
	LChild->W = PResourcesGUI->TranslationYW;LChild->Manage = LIncremental;LChild++;
	LChild->W = PResourcesGUI->TranslationZW;LChild->Manage = LIncremental;LChild++;

	LChild->W = PResourcesGUI->RotationXW;LChild->Manage = LIncremental;LChild++;
	LChild->W = PResourcesGUI->RotationYW;LChild->Manage = LIncremental;LChild++;
	LChild->W = PResourcesGUI->RotationZW;LChild->Manage = LIncremental;LChild++;

	LChild->W = PResourcesGUI->ScalingXW;LChild->Manage = LIncremental;LChild++;
	LChild->W = PResourcesGUI->ScalingYW;LChild->Manage = LIncremental;LChild++;
	LChild->W = PResourcesGUI->ScalingZW;LChild->Manage = LIncremental;LChild++;

	LChild->W = PResourcesGUI->OffsetMatrixW;LChild->Manage = LOverShape;LChild++;

	if(LOverShape && PInfo->Shape && EObject_IsSubClass(PInfo->Shape, E3dSpline))
	{
		LChild->W = PResourcesGUI->PathNormalGUI.TopMatrixW;          LChild->Manage = TRUE;LChild++;
		LChild->W = PResourcesGUI->PathNormalGUI.AxisSelector.MatrixW;LChild->Manage = TRUE;LChild++;
	}
	else
	{
		LChild->W = PResourcesGUI->PathNormalGUI.TopMatrixW;          LChild->Manage = FALSE;LChild++;
		LChild->W = PResourcesGUI->PathNormalGUI.AxisSelector.MatrixW;LChild->Manage = FALSE;LChild++;
	}

	{
		const EBool  LScaleUBySplineEnabled = LOverShape && PInfo->Shape && EObject_IsSubClass(PInfo->Shape, E3dSpline);
		LChild->W = PResourcesGUI->ScaleUBySplineMatrixW;LChild->Manage = LScaleUBySplineEnabled;LChild++;
	}

	if(LChild - LChildren) XeWidgets_ChangeManage(LChildren, LChild - LChildren);

	{
		XeWidget  LWs[2] = { PResourcesGUI->ShapePickW, PResourcesGUI->ShapeShowW };
		if(LOverShape) XeWidgets_Manage(LWs, 2);
		else XeWidgets_Unmanage(LWs, 2);
	}
}


static void _DCB_Lock(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dNodePanel*  LPanel = (E3dNodePanel*)PClientData;E3dNode*  LNode = LPanel->Node;

	E3dDuplicateWithRepeatInfo*  LInfo = (E3dDuplicateWithRepeatInfo*)E3dNodeComponent_ByClass(LNode, _DuplicateWithRepeatComponentClass, FALSE);

	if(((XeToggleButtonCallbackStruct*)PCallData)->Set) LInfo->Flags &= ~(EComponentANIMATABLE|EComponentRESPOND_TO_INPUT_CHANGE);
	else LInfo->Flags |= EComponentANIMATABLE|EComponentRESPOND_TO_INPUT_CHANGE;

//	_DuplicateRep(E3d_Scene, LInfo->NodeToDuplicate, LNode, LInfo);
}



//========================================
// Dialog-box callback
//========================================
static void _DCB_Instance(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dNodePanel*  LPanel = (E3dNodePanel*)PClientData;
	E3dNode*       LNode = LPanel->Node;if(LNode)
	{
		E3dDuplicateWithRepeatInfo*  LInfo = (E3dDuplicateWithRepeatInfo*)E3dNodeComponent_ByClass(LNode, _DuplicateWithRepeatComponentClass, FALSE);if(LInfo)
		{
			LInfo->Instance = ((XeToggleButtonCallbackStruct*)PCallData)->Set;

			_DuplicateRep(E3d_Scene, LInfo->NodeToDuplicate, LNode, LInfo);
		}
	}
}


//========================================
// Dialog-box callback
//========================================
static void _DCB_Copy_materials(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dNodePanel*  LPanel = (E3dNodePanel*)PClientData;
	E3dNode*       LNode = LPanel->Node;if(LNode)
	{
		E3dDuplicateWithRepeatInfo*  LInfo = (E3dDuplicateWithRepeatInfo*)E3dNodeComponent_ByClass(LNode, _DuplicateWithRepeatComponentClass, FALSE);

		if(LInfo)
		{
			if(((XeToggleButtonCallbackStruct*)PCallData)->Set) LInfo->CloneFlags |= E3dCopyMATERIALS;
			else LInfo->CloneFlags &= ~E3dCopyMATERIALS;

			_DuplicateRep(E3d_Scene, LInfo->NodeToDuplicate, LNode, LInfo);
		}
	}
}


//========================================
// Dialog-box callback
//========================================
static void _DCB_Incremental(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dNodePanel*  LPanel = (E3dNodePanel*)PClientData;
	E3dNode*       LNode = LPanel->Node;

	if(((XeToggleButtonCallbackStruct*)PCallData)->Set)
	{
		E3dDuplicateWithRepeatInfo*  LInfo = (E3dDuplicateWithRepeatInfo*)E3dNodeComponent_ByClass(LNode, _DuplicateWithRepeatComponentClass, FALSE);

		if(LInfo->Mode != E3dINCREMENTAL)
		{
			LInfo->Mode = E3dINCREMENTAL;

			XeComponentClassDrawer*  LDrawer = XeInfoPanel_GetFolderByClass((XeInfoPanel*)LPanel, _DuplicateWithRepeatComponentClass);if(LDrawer)
			{
				E3dResourcesGUI*  LResourcesGUI = (E3dResourcesGUI*)(LDrawer->ResourcesGUI);
				_SetGUI(LInfo, LResourcesGUI);
			}
		}
	}
}


//========================================
// Dialog-box callback
//========================================
static void _DCB_OverShape(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dNodePanel*  LPanel = (E3dNodePanel*)PClientData;
	E3dNode*       LNode = LPanel->Node;

	if(((XeToggleButtonCallbackStruct*)PCallData)->Set)
	{
		E3dDuplicateWithRepeatInfo*  LInfo = (E3dDuplicateWithRepeatInfo*)E3dNodeComponent_ByClass(LNode, _DuplicateWithRepeatComponentClass, FALSE);

		if(LInfo->Mode != E3dOVER_SHAPE)
		{
			LInfo->Mode = E3dOVER_SHAPE;

			XeComponentClassDrawer*  LDrawer = XeInfoPanel_GetFolderByClass((XeInfoPanel*)LPanel, _DuplicateWithRepeatComponentClass);if(LDrawer)
			{
				E3dResourcesGUI*  LResourcesGUI = (E3dResourcesGUI*)(LDrawer->ResourcesGUI);
				_SetGUI(LInfo, LResourcesGUI);
			}

		}
	}
}


//========================================
// Dialog-box callback
//========================================
static void _DCB_Duplicates(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dNodePanel*  LPanel = (E3dNodePanel*)PClientData;
	E3dNode*       LNode = LPanel->Node;if(LNode)
	{
		E3dDuplicateWithRepeatInfo*  LInfo = (E3dDuplicateWithRepeatInfo*)E3dNodeComponent_ByClass(LNode, _DuplicateWithRepeatComponentClass, FALSE);if(LInfo)
		{
			LInfo->NumOfDuplicates = ((XeScaleCallbackStruct*)PCallData)->Value.i32;

			_DuplicateRep(E3d_Scene, LInfo->NodeToDuplicate, LNode, LInfo);
		}
	}
}


//========================================
// Dialog-box callback
//========================================
static void _DCB_OrientAlongObject(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dNodePanel*  LPanel = (E3dNodePanel*)PClientData;
	E3dNode*       LNode = LPanel->Node;

	E3dDuplicateWithRepeatInfo*  LInfo = (E3dDuplicateWithRepeatInfo*)E3dNodeComponent_ByClass(LNode, _DuplicateWithRepeatComponentClass, FALSE);

	LInfo->OrientAlongObject = ((XeToggleButtonCallbackStruct*)PCallData)->Set;
	_DuplicateRep(E3d_Scene, LInfo->NodeToDuplicate, LNode, LInfo);
}

//========================================
// Dialog-box callback
//========================================
static void _DCB_VisibleOnly(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dNodePanel*  LPanel = (E3dNodePanel*)PClientData;
	E3dNode*       LNode = LPanel->Node;

	E3dDuplicateWithRepeatInfo*  LInfo = (E3dDuplicateWithRepeatInfo*)E3dNodeComponent_ByClass(LNode, _DuplicateWithRepeatComponentClass, FALSE);

	LInfo->VisibleOnly = ((XeToggleButtonCallbackStruct*)PCallData)->Set;
	_DuplicateRep(E3d_Scene, LInfo->NodeToDuplicate, LNode, LInfo);
}

static void _DCB_MakeModelsVisible(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dNodePanel*  LPanel = (E3dNodePanel*)PClientData;
	E3dNode*       LNode = LPanel->Node;

	E3dDuplicateWithRepeatInfo*  LInfo = (E3dDuplicateWithRepeatInfo*)E3dNodeComponent_ByClass(LNode, _DuplicateWithRepeatComponentClass, FALSE);

	LInfo->MakeModelsVisible = ((XeToggleButtonCallbackStruct*)PCallData)->Set;
	_DuplicateRep(E3d_Scene, LInfo->NodeToDuplicate, LNode, LInfo);
}


static void _DCB_Cameras(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dNodePanel*  LPanel = (E3dNodePanel*)PClientData;E3dNode*  LNode = LPanel->Node;

	E3dDuplicateWithRepeatInfo*  LInfo = (E3dDuplicateWithRepeatInfo*)E3dNodeComponent_ByClass(LNode, _DuplicateWithRepeatComponentClass, FALSE);

	if(((XeToggleButtonCallbackStruct*)PCallData)->Set) LInfo->ObjectTypeFlags |= E3dItemCAMERA;
	else LInfo->ObjectTypeFlags &= ~E3dItemCAMERA;
	_DuplicateRep(E3d_Scene, LInfo->NodeToDuplicate, LNode, LInfo);
}

static void _DCB_Lights(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dNodePanel*  LPanel = (E3dNodePanel*)PClientData;E3dNode*  LNode = LPanel->Node;

	E3dDuplicateWithRepeatInfo*  LInfo = (E3dDuplicateWithRepeatInfo*)E3dNodeComponent_ByClass(LNode, _DuplicateWithRepeatComponentClass, FALSE);

	if(((XeToggleButtonCallbackStruct*)PCallData)->Set) LInfo->ObjectTypeFlags |= E3dItemLIGHT;
	else LInfo->ObjectTypeFlags &= ~E3dItemLIGHT;
	_DuplicateRep(E3d_Scene, LInfo->NodeToDuplicate, LNode, LInfo);
}

static void _DCB_PointSets(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dNodePanel*  LPanel = (E3dNodePanel*)PClientData;E3dNode*  LNode = LPanel->Node;

	E3dDuplicateWithRepeatInfo*  LInfo = (E3dDuplicateWithRepeatInfo*)E3dNodeComponent_ByClass(LNode, _DuplicateWithRepeatComponentClass, FALSE);

	if(((XeToggleButtonCallbackStruct*)PCallData)->Set) LInfo->ObjectTypeFlags |= E3dItemPOINTSET;
	else LInfo->ObjectTypeFlags &= ~E3dItemPOINTSET;
	_DuplicateRep(E3d_Scene, LInfo->NodeToDuplicate, LNode, LInfo);
}

static void _DCB_Splines(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dNodePanel*  LPanel = (E3dNodePanel*)PClientData;E3dNode*  LNode = LPanel->Node;

	E3dDuplicateWithRepeatInfo*  LInfo = (E3dDuplicateWithRepeatInfo*)E3dNodeComponent_ByClass(LNode, _DuplicateWithRepeatComponentClass, FALSE);

	if(((XeToggleButtonCallbackStruct*)PCallData)->Set) LInfo->ObjectTypeFlags |= E3dItemSPLINE;
	else LInfo->ObjectTypeFlags &= ~E3dItemSPLINE;
	_DuplicateRep(E3d_Scene, LInfo->NodeToDuplicate, LNode, LInfo);
}

static void _DCB_Faces(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dNodePanel*  LPanel = (E3dNodePanel*)PClientData;E3dNode*  LNode = LPanel->Node;

	E3dDuplicateWithRepeatInfo*  LInfo = (E3dDuplicateWithRepeatInfo*)E3dNodeComponent_ByClass(LNode, _DuplicateWithRepeatComponentClass, FALSE);

	if(((XeToggleButtonCallbackStruct*)PCallData)->Set) LInfo->ObjectTypeFlags |= E3dItemFACE;
	else LInfo->ObjectTypeFlags &= ~E3dItemFACE;
	_DuplicateRep(E3d_Scene, LInfo->NodeToDuplicate, LNode, LInfo);
}

static void _DCB_Meshes(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dNodePanel*  LPanel = (E3dNodePanel*)PClientData;E3dNode*  LNode = LPanel->Node;

	E3dDuplicateWithRepeatInfo*  LInfo = (E3dDuplicateWithRepeatInfo*)E3dNodeComponent_ByClass(LNode, _DuplicateWithRepeatComponentClass, FALSE);

	if(((XeToggleButtonCallbackStruct*)PCallData)->Set) LInfo->ObjectTypeFlags |= E3dItemMESH;
	else LInfo->ObjectTypeFlags &= ~E3dItemMESH;
	_DuplicateRep(E3d_Scene, LInfo->NodeToDuplicate, LNode, LInfo);
}

//========================================
// Dialog-box callback
//========================================
static void _DCB_TranslationX(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dNodePanel*  LPanel = (E3dNodePanel*)PClientData;
	E3dNode*       LNode = LPanel->Node;if(LNode)
	{
		E3dDuplicateWithRepeatInfo*  LInfo = (E3dDuplicateWithRepeatInfo*)E3dNodeComponent_ByClass(LNode, _DuplicateWithRepeatComponentClass, FALSE);if(LInfo)
		{
			E3dTransformSet*  LTransformSet = &LInfo->TransformIncrement;

			LTransformSet->Translation.X = ((XeScaleCallbackStruct*)PCallData)->Value.m_Coordinate;

			_DuplicateRep(E3d_Scene, LInfo->NodeToDuplicate, LNode, LInfo);
		}
	}
}


static void _DCB_TranslationY(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dNodePanel*  LPanel = (E3dNodePanel*)PClientData;
	E3dNode*       LNode = LPanel->Node;if(LNode)
	{
		E3dDuplicateWithRepeatInfo*  LInfo = (E3dDuplicateWithRepeatInfo*)E3dNodeComponent_ByClass(LNode, _DuplicateWithRepeatComponentClass, FALSE);if(LInfo)
		{
			E3dTransformSet*  LTransformSet = &LInfo->TransformIncrement;

			LTransformSet->Translation.Y = ((XeScaleCallbackStruct*)PCallData)->Value.m_Coordinate;

			_DuplicateRep(E3d_Scene, LInfo->NodeToDuplicate, LNode, LInfo);
		}
	}
}


static void _DCB_TranslationZ(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dNodePanel*  LPanel = (E3dNodePanel*)PClientData;
	E3dNode*       LNode = LPanel->Node;if(LNode)
	{
		E3dDuplicateWithRepeatInfo*  LInfo = (E3dDuplicateWithRepeatInfo*)E3dNodeComponent_ByClass(LNode, _DuplicateWithRepeatComponentClass, FALSE);if(LInfo)
		{
			E3dTransformSet*  LTransformSet = &LInfo->TransformIncrement;

			LTransformSet->Translation.Z = ((XeScaleCallbackStruct*)PCallData)->Value.m_Coordinate;

			_DuplicateRep(E3d_Scene, LInfo->NodeToDuplicate, LNode, LInfo);
		}
	}
}


static void _DCB_RotationX(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dNodePanel*  LPanel = (E3dNodePanel*)PClientData;
	E3dNode*       LNode = LPanel->Node;if(LNode)
	{
		E3dDuplicateWithRepeatInfo*  LInfo = (E3dDuplicateWithRepeatInfo*)E3dNodeComponent_ByClass(LNode, _DuplicateWithRepeatComponentClass, FALSE);if(LInfo)
		{
			E3dTransformSet*  LTransformSet = &LInfo->TransformIncrement;

			LTransformSet->EulerRotation.Angles.X = ((XeScaleCallbackStruct*)PCallData)->Value.m_Coordinate;

			_DuplicateRep(E3d_Scene, LInfo->NodeToDuplicate, LNode, LInfo);
		}
	}
}


static void _DCB_RotationY(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dNodePanel*  LPanel = (E3dNodePanel*)PClientData;
	E3dNode*       LNode = LPanel->Node;if(LNode)
	{
		E3dDuplicateWithRepeatInfo*  LInfo = (E3dDuplicateWithRepeatInfo*)E3dNodeComponent_ByClass(LNode, _DuplicateWithRepeatComponentClass, FALSE);if(LInfo)
		{
			E3dTransformSet*  LTransformSet = &LInfo->TransformIncrement;

			LTransformSet->EulerRotation.Angles.Y = ((XeScaleCallbackStruct*)PCallData)->Value.m_Coordinate;

			_DuplicateRep(E3d_Scene, LInfo->NodeToDuplicate, LNode, LInfo);
		}
	}
}


static void _DCB_RotationZ(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dNodePanel*  LPanel = (E3dNodePanel*)PClientData;
	E3dNode*       LNode = LPanel->Node;if(LNode)
	{
		E3dDuplicateWithRepeatInfo*  LInfo = (E3dDuplicateWithRepeatInfo*)E3dNodeComponent_ByClass(LNode, _DuplicateWithRepeatComponentClass, FALSE);if(LInfo)
		{
			E3dTransformSet*  LTransformSet = &LInfo->TransformIncrement;

			LTransformSet->EulerRotation.Angles.Z = ((XeScaleCallbackStruct*)PCallData)->Value.m_Coordinate;

			_DuplicateRep(E3d_Scene, LInfo->NodeToDuplicate, LNode, LInfo);
		}
	}
}


static void _DCB_ScalingX(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dNodePanel*  LPanel = (E3dNodePanel*)PClientData;
	E3dNode*       LNode = LPanel->Node;if(LNode)
	{
		E3dDuplicateWithRepeatInfo*  LInfo = (E3dDuplicateWithRepeatInfo*)E3dNodeComponent_ByClass(LNode, _DuplicateWithRepeatComponentClass, FALSE);if(LInfo)
		{
			E3dTransformSet*  LTransformSet = &LInfo->TransformIncrement;

			LTransformSet->Scaling.X = ((XeScaleCallbackStruct*)PCallData)->Value.m_Coordinate;

			_DuplicateRep(E3d_Scene, LInfo->NodeToDuplicate, LNode, LInfo);
		}
	}
}


static void _DCB_ScalingY(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dNodePanel*  LPanel = (E3dNodePanel*)PClientData;
	E3dNode*       LNode = LPanel->Node;if(LNode)
	{
		E3dDuplicateWithRepeatInfo*  LInfo = (E3dDuplicateWithRepeatInfo*)E3dNodeComponent_ByClass(LNode, _DuplicateWithRepeatComponentClass, FALSE);if(LInfo)
		{
			E3dTransformSet*  LTransformSet = &LInfo->TransformIncrement;

			LTransformSet->Scaling.Y = ((XeScaleCallbackStruct*)PCallData)->Value.m_Coordinate;

			_DuplicateRep(E3d_Scene, LInfo->NodeToDuplicate, LNode, LInfo);
		}
	}
}


static void _DCB_ScalingZ(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dNodePanel*  LPanel = (E3dNodePanel*)PClientData;
	E3dNode*       LNode = LPanel->Node;if(LNode)
	{
		E3dDuplicateWithRepeatInfo*  LInfo = (E3dDuplicateWithRepeatInfo*)E3dNodeComponent_ByClass(LNode, _DuplicateWithRepeatComponentClass, FALSE);if(LInfo)
		{
			E3dTransformSet*  LTransformSet = &LInfo->TransformIncrement;

			LTransformSet->Scaling.Z = ((XeScaleCallbackStruct*)PCallData)->Value.m_Coordinate;

			_DuplicateRep(E3d_Scene, LInfo->NodeToDuplicate, LNode, LInfo);
		}
	}
}


static void _DCB_Offset(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dNodePanel*  LPanel = (E3dNodePanel*)PClientData;
	E3dNode*       LNode = LPanel->Node;if(LNode)
	{
		E3dDuplicateWithRepeatInfo*  LInfo = (E3dDuplicateWithRepeatInfo*)E3dNodeComponent_ByClass(LNode, _DuplicateWithRepeatComponentClass, FALSE);if(LInfo)
		{
			LInfo->Offset = ((XeScaleCallbackStruct*)PCallData)->Value.m_Coordinate;

			_DuplicateRep(E3d_Scene, LInfo->NodeToDuplicate, LNode, LInfo);
		}
	}
}



//================================================================
// Pick path Spline callback
//================================================================
static void _PickCB_DuplicateOverShape(E3dPickCallbackStruct* PPickStruct, EPointer PClientData,  const EIndex PNumOfItems, E3dItem* PPickedItems)
{
	XeApp_Sync(Xe_App);

	if(PPickStruct->Reason == E3dCR_ABORT) return;

// Cancel
//
	if(PPickStruct->Button == XeButton3)
	{
		E3dp_PickEnd();
		return;
	}

	switch(PPickStruct->Button)
	{
		case XeButton1:
			if(PNumOfItems)
			{
				E3dItem*  LItem = PPickedItems;

				if(LItem->Item.Type == E3dItemSPLINE || LItem->Item.Type == E3dItemMESH || LItem->Item.Type == E3dItemMODEL)
				{
					if(_TmpInfo)
					{
// Pick new Shape for existing DuplicateWithRepeat Node
//
						E3dDuplicateWithRepeatInfo*  LInfo = _TmpInfo;
						E3dNode*                     LUberRootNode = LInfo->Node;

						switch(LItem->Item.Type)
						{
							case E3dItemSPLINE:
								{
									E3dSplineItem*  LSplineItem = (E3dSplineItem*)LItem;
									E3dObject*      LObject = LSplineItem->Object;

									if(EObject_IsSubClass(LObject, E3dSpline))
									{
										E_AssignToMember(LSplineItem->Node, LInfo,Model);
										E_AssignToMember(LObject, LInfo,Shape);

										E3dNode_Edit(LUberRootNode, _DuplicateWithRepeatComponentClass);
										_DuplicateRep(E3d_Scene, LInfo->NodeToDuplicate, LUberRootNode, LInfo);
									}
								}
							break;
						}


						E3dp_PickEnd();

						_TmpInfo  = NULL;
						_TmpPanel = NULL;
					}
					else
					{
						EListAI_s(, E3dNodePtr, LSelectedNodes);const EIndex  LNSelectedNodes = E3dScene_GetSelectedNodes(E3d_Scene, E3dNodeSelNODE|E3dNodeSelBRANCH_ROOT, FALSE,  &LSelectedNodes);if(LNSelectedNodes > 0)
						{
							E3dNode*  LRootNodeToDuplicate = LSelectedNodes.Items[0];
							E3dNode*  LUberRootNode;

							{
								char*  LNewRootName = LRootNodeToDuplicate->Name ? EMalloc(strlen(LRootNodeToDuplicate->Name) + 12) : EMalloc(12);

								E_NewCAssignNoChkNoFree(E3dModel_class, LUberRootNode);
								sprintf(LNewRootName, "%s-copies", LRootNodeToDuplicate->Name);
								EObject_SetName(LUberRootNode, EStrDup(LNewRootName), FALSE);
							}

							EList_FreeA_s(LSelectedNodes);

							E3dDuplicateWithRepeatInfo*  LInfo = (E3dDuplicateWithRepeatInfo*)E3dNodeComponent_Add(LUberRootNode, _DuplicateWithRepeatComponentClass);

							LInfo->Mode = E3dOVER_SHAPE;

							E_AssignToMember(LRootNodeToDuplicate, LInfo,NodeToDuplicate);

							switch(LItem->Item.Type)
							{
								case E3dItemSPLINE:
									{
										E3dSplineItem*  LSplineItem = (E3dSplineItem*)LItem;
										E3dObject*      LObject = LSplineItem->Object;

										if(EObject_IsSubClass(LObject, E3dSpline))
										{
											E_AssignToMember(LSplineItem->Node, LInfo,Model);
											E_AssignToMember(LObject, LInfo,Shape);

											E3dNode_Edit(LUberRootNode, _DuplicateWithRepeatComponentClass);
											_DuplicateRep(E3d_Scene, LInfo->NodeToDuplicate, LUberRootNode, LInfo);
										}
									}
								break;
							}

// Just in case _DuplicateRep failed
//
							E_Delete(LUberRootNode);
						}
						else E3dp_PrintMessage(ELogWARNING, 5000, "Select Model(s) to duplicate");
						E3dp_PickEnd();
					}
				}
			}
		break;
	}
}


static void _RequestShapePick(void)
{
	const E3dpPrefs*  LPrefs = &E3dp_Prefs;
	const uint32_t    LLabelRGBAp8Color = Xe_ForegroundRGBA8pColor;

	E3dp_PickRequest(XeString_NewVa(TRUE,
	 XeFONT, XeFontDEFAULT,  XeCOLOR_RGBA, LLabelRGBAp8Color,  XeTEXT,  "Duplicate over Shape    ",
	 E3dp_LMouseButtonS,     XeTEXT,  ": pick object   ",
	 E3dp_RMouseButtonEscS,  XeTEXT,  ": cancel",
	 0), E3dItemSPLINE|E3dItemMESH,  _PickCB_DuplicateOverShape, NULL,  0, FALSE);
}


static void _DCB_ShapePick(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dNodePanel*  LPanel = (E3dNodePanel*)PClientData;
	E3dNode*       LNode = LPanel->Node;if(LNode)
	{
		E3dDuplicateWithRepeatInfo*  LInfo = (E3dDuplicateWithRepeatInfo*)E3dNodeComponent_ByClass(LNode, _DuplicateWithRepeatComponentClass, FALSE);if(LInfo)
		{
			_TmpInfo = LInfo;
			_TmpPanel = LPanel;
			_RequestShapePick();
		}
	}
}


static void _DCB_ShapeShow(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dNodePanel*  LPanel = (E3dNodePanel*)PClientData;
	EChangeFlags   LChanged = 0;
	E3dNode*       LNode = LPanel->Node;if(LNode)
	{
		E3dDuplicateWithRepeatInfo*  LInfo = (E3dDuplicateWithRepeatInfo*)E3dNodeComponent_ByClass(LNode, _DuplicateWithRepeatComponentClass, FALSE);if(LInfo)
		{
			E3dObject*  LShape = E_DynamicCast(LInfo->Shape, E3dObject);if(LShape)
			{
				if(E3dSel_ObjectSelect(E3d_Scene, LShape, FALSE, FALSE))
				{
					const E3dpPrefs*  LPrefs = &E3dp_Prefs;
					E3dp_PrintCompoundMessageVa(ELogMESSAGE, 3000,  XeFONT, XeFontDEFAULT,  XeTEXT, "Selected Spline ",  E3dp_ObjectS(LShape->Name ? LShape->Name : "-"),  0);\

					LChanged |= E3dChgSELECTION;
				}
			}
		}
	}

	if(LChanged)
	{
		E3dWindows_RedrawByChangeFlags(E3d_Scene, LChanged);
		E3dTransformPanel_Update(NULL,  E3dDoALL, E3dDoALL, E3dDoALL,  E3dpDO_NOT_CHANGE,  E3dTrPanelALL);
	}
	else E3dp_PrintMessage(ELogWARNING, 5000, "No 'duplicate over' shape was set.");
}


//========================================================
// Add animation KeyFrame for the Offset parameter
//========================================================
static void _CB_OffsetAddKeyFrame(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dNodePanel*  LPanel = (E3dNodePanel*)PClientData;
	E3dNode*       LNode = LPanel->Node;if(LNode)
	{
		E3dDuplicateWithRepeatInfo*  LInfo = (E3dDuplicateWithRepeatInfo*)E3dNodeComponent_ByClass(LNode, _DuplicateWithRepeatComponentClass, FALSE);if(LInfo)
		{
			E3dScene*         LScene = E3d_Scene;
			const EResource*  LResource = EResource_ByName(&_DuplicateWithRepeatResources, "Offset");if(LResource)
			{
				EtTimePanel*                  LTimePanel = E3dp_TimePanel;
				const double                  LTime = LTimePanel->Time, LTimeEpsilon = 0.5 / LTimePanel->FramesPerSecond;
				E3dKeyFrameAnimation*         LAnimation = E3dScene_KeyFrameAnimationAdd(LScene, (ECoreObject*)LNode, _AnimationSemantic_DuplicateOverShape, LResource, 0);
				E3dKeyFrameAnimationChannel*  LChannel;

				E3dKeyFrameAnimation_AddKey(LAnimation, &LChannel, "Offset", Ec_RGBAiToRGBAp8(&E3dp_Prefs.AxisWRGBAiColor),  LTime, LTimeEpsilon, LInfo->Offset, E3dSplineBEZIER, E3dp_Prefs.NewAnimKeyEaseInOut,  (EObject*)LNode, (EObject*)LInfo, LResource, 0,  TRUE);
				LChannel->ChangeFlags = E3dChgNodeTRANSFORM;
				E3dp_PrintMessage(ELogMESSAGE, 5000, "Keyframe added");
				E3dWindows_RedrawByChangeFlags(LScene, E3dChgANIMATION);
			}
		}
	}
}



static void _CB_PathNormalFixed(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dNodePanel*  LPanel = (E3dNodePanel*)PClientData;
	E3dNode*       LNode = LPanel->Node;if(LNode)
	{
		E3dDuplicateWithRepeatInfo*  LInfo = (E3dDuplicateWithRepeatInfo*)E3dNodeComponent_ByClass(LNode, _DuplicateWithRepeatComponentClass, FALSE);if(LInfo)
		{
			if(((XeToggleButtonCallbackStruct*)PCallData)->Set)
			{
				LInfo->PathNormalSource = E3dSplineNormalFIXED;

				XeComponentClassDrawer*  LDrawer = XeInfoPanel_GetFolderByClass((XeInfoPanel*)LPanel, _DuplicateWithRepeatComponentClass);if(LDrawer)
				{
					E3dResourcesGUI*  LResourcesGUI = (E3dResourcesGUI*)(LDrawer->ResourcesGUI);
					XeWidget_Manage(LResourcesGUI->PathNormalGUI.AxisSelector.MatrixW);
				}

				_DuplicateRep(E3d_Scene, LInfo->NodeToDuplicate, LNode, LInfo);
			}
		}
	}
}


static void _CB_PathNormalCV(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dNodePanel*  LPanel = (E3dNodePanel*)PClientData;
	E3dNode*       LNode = LPanel->Node;if(LNode)
	{
		E3dDuplicateWithRepeatInfo*  LInfo = (E3dDuplicateWithRepeatInfo*)E3dNodeComponent_ByClass(LNode, _DuplicateWithRepeatComponentClass, FALSE);if(LInfo)
		{
			if(((XeToggleButtonCallbackStruct*)PCallData)->Set)
			{
				LInfo->PathNormalSource = E3dSplineNormalCV;

				XeComponentClassDrawer*  LDrawer = XeInfoPanel_GetFolderByClass((XeInfoPanel*)LPanel, _DuplicateWithRepeatComponentClass);if(LDrawer)
				{
					E3dResourcesGUI*  LResourcesGUI = (E3dResourcesGUI*)(LDrawer->ResourcesGUI);
					XeWidget_Unmanage(LResourcesGUI->PathNormalGUI.AxisSelector.MatrixW);
				}

				_DuplicateRep(E3d_Scene, LInfo->NodeToDuplicate, LNode, LInfo);
			}
		}
	}
}


static void _CB_PathNormalAuto(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dNodePanel*  LPanel = (E3dNodePanel*)PClientData;
	E3dNode*       LNode = LPanel->Node;if(LNode)
	{
		E3dDuplicateWithRepeatInfo*  LInfo = (E3dDuplicateWithRepeatInfo*)E3dNodeComponent_ByClass(LNode, _DuplicateWithRepeatComponentClass, FALSE);if(LInfo)
		{
			if(((XeToggleButtonCallbackStruct*)PCallData)->Set)
			{
				LInfo->PathNormalSource = E3dSplineNormalAUTO;

				XeComponentClassDrawer*  LDrawer = XeInfoPanel_GetFolderByClass((XeInfoPanel*)LPanel, _DuplicateWithRepeatComponentClass);if(LDrawer)
				{
					E3dResourcesGUI*  LResourcesGUI = (E3dResourcesGUI*)(LDrawer->ResourcesGUI);
					XeWidget_Unmanage(LResourcesGUI->PathNormalGUI.AxisSelector.MatrixW);
				}

				_DuplicateRep(E3d_Scene, LInfo->NodeToDuplicate, LNode, LInfo);
			}
		}
	}
}


//========================================
// AxisSelector callback
//========================================
static void _CB_PathNormalAxisSelector(ECoreObject* PStruct, EPointer PClientData, ECallbackStruct* PCallData)
{
	E3dNodePanel*  LPanel = (E3dNodePanel*)PClientData;
	E3dNode*       LNode = LPanel->Node;if(LNode)
	{
		E3dDuplicateWithRepeatInfo*  LInfo = (E3dDuplicateWithRepeatInfo*)E3dNodeComponent_ByClass(LNode, _DuplicateWithRepeatComponentClass, FALSE);if(LInfo)
		{
			E3dpAxisSelector*  LAxisSelector = (E3dpAxisSelector*)PStruct;

			LInfo->PathNormal = LAxisSelector->Axis;

			_DuplicateRep(E3d_Scene, LInfo->NodeToDuplicate, LNode, LInfo);
		}
	}
}


#include "PathNormalGUI.ci"

static void _GUI(E3dNode* PNode, E3dNodeComponent* PInfo, XeInfoPanel* PPanel, void* PDrawer, const int PAction)
{
	XeComponentClassDrawer*      LDrawer = (XeComponentClassDrawer*)PDrawer;
	E3dDuplicateWithRepeatInfo*  LInfo = (E3dDuplicateWithRepeatInfo*)PInfo;
	E3dResourcesGUI*             LResourcesGUI;

	switch(PAction)
	{
		case E_UPDATE:
			if(LInfo)
			{
				if(LDrawer->ResourcesGUI == NULL)
				{
					E3dTransformSet*  LTransformSet = &LInfo->TransformIncrement;
					XeWidget          LTopMatrixW = LDrawer->Drawer.DrawerW, LW;
					EArg              Args[256];int  ArgCnt;

					XeWidget          LIndentWs[16];int  LIndentN;      // For indenting the ScaleWidgets
					XeWidget          LTraverseWs[16];int  LTraverseN;  // For TextField traversal

					ENew(LResourcesGUI);LDrawer->ResourcesGUI = LResourcesGUI;

					EXeStart;
					EXeSetArg(XeNShadowThickness, 0);EXeSetArg(XeNHighlightThickness, 0);
					EXeSetArg(XeNMarginSizeX, 2);
					EXeSetArg(XeNMarginBottom, 0);
					EXeSetArg(XeNSet, (LInfo->Flags & (EComponentANIMATABLE|EComponentRESPOND_TO_INPUT_CHANGE)) != (EComponentANIMATABLE|EComponentRESPOND_TO_INPUT_CHANGE));
					EXeSetArg(XeNResizableY, FALSE);
					XeToolTip8("Prevent animation of the parameters, or change in the input Model(s) from triggering an update.\nSet this to ON, if you don't want the copies to be rebuilt automatically.\nFor example, you may have made manual changes to the copies that you don't want to lose.");
					LResourcesGUI->LockW = LW = XeWidget_ManagedNew("Lock", XeToggleButton_class, LTopMatrixW,  Args, ArgCnt);
					XeAddCallback(LW, XeNValueChangedCallback, _DCB_Lock, (EPointer)PPanel);

					{
						EXeStart;
						EXeSetArg(XeNOrientation, XeHORIZONTAL);
						EXeSetArg(XeNMarginSizeX, 2);
						EXeSetArg(XeNSpacingX, 8);
						EXeSetArg(XeNResizableY, FALSE);
						EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNAlignmentX, XeAlignBEGINNING);
						XeWidget  LMatrixW = XeWidget_ManagedNew("", XeMatrixWidget_class, LTopMatrixW,  Args, ArgCnt);

						EXeStart;
						EXeSetArg(XeNShadowThickness, 0);
						EXeSetArg(XeNHighlightThickness, 0);
						EXeSetArg(XeNMarginBottom, 0);
						EXeSetArg(XeNSet, LInfo->Instance);
						EXeSetArg(XeNResizableY, FALSE);
						LResourcesGUI->InstanceW = LW = XeWidget_ManagedNew("Instance", XeToggleButton_class, LMatrixW,  Args, ArgCnt);
						XeAddCallback(LW, XeNValueChangedCallback, _DCB_Instance, (EPointer)PPanel);

						EXeStart;
						EXeSetArg(XeNShadowThickness, 0);
						EXeSetArg(XeNHighlightThickness, 0);
						EXeSetArg(XeNResizableY, FALSE);
						LResourcesGUI->CopyMaterialsW = LW = XeWidget_ManagedNew("Copy Materials", XeToggleButton_class, LMatrixW,  Args, ArgCnt);
						XeAddCallback(LW, XeNValueChangedCallback, _DCB_Copy_materials, (EPointer)PPanel);
						XeToggleButton_SetState(LW, (EBool)((LInfo->CloneFlags & E3dCopyMATERIALS) != 0), FALSE);
					}


					LIndentN = 0;LTraverseN = 0;
					EXeStart;
					EXeSetArg(XeNMarginSizeX, 2);EXeSetArg(XeNMarginTop, 4);
					EXeSetArg(XeNTextFieldDigits, 5);
					EXeSetArg(XeNScaleMinimum, 1);EXeSetArg(XeNScaleMaximum, 20);
					EXeSetArg(XeNMinimum, 1);EXeSetArg(XeNMaximum, 100000);EXeSetArg(XeNValue, LInfo->NumOfDuplicates);
					EXeSetArg(XeNIncrement, 1);
					EXeSetArg(XeNResizableY, FALSE);
					LTraverseWs[LTraverseN++] = LResourcesGUI->DuplicatesScaleW = LW = XeWidget_ManagedNew("Copies", XeScaleWidget_class, LTopMatrixW,  Args, ArgCnt);
					XeAddCallback(LW, XeNValueChangedCallback, _DCB_Duplicates, (EPointer)PPanel);

					{
						EXeStart;
						EXeSetArg(XeNShadowThickness, 2);
						EXeSetArg(XeNShadowType, XeShadowETCHED_IN);
						EXeSetArg(XeNResizableY, FALSE);
						XeWidget_ManagedNew("", XeSeparatorWidget_class, LTopMatrixW,  Args, ArgCnt);

						EXeStart;
						EXeSetArg(XeNRadioBehavior, TRUE);
						EXeSetArg(XeNOrientation, XeHORIZONTAL);
						EXeSetArg(XeNMarginSizeX, 2);EXeSetArg(XeNMarginSizeY, 4);
						EXeSetArg(XeNSpacingX, 0);
						EXeSetArg(XeNSelfResizePolicyY, XeGROW);
						EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNAlignmentX, XeAlignBEGINNING);
						EXeSetArg(XeNResizableY, FALSE);
						XeWidget  LRadioMatrixW = XeWidget_ManagedNew("", XeMatrixWidget_class, LTopMatrixW,  Args, ArgCnt);


						EXeStart;
						EXeSetArg(XeNShadowThickness, 0);
						EXeSetArg(XeNHighlightThickness, 0);
						EXeSetArg(XeNSet, LInfo->Mode == E3dINCREMENTAL);
						EXeSetArg(XeNResizableY, FALSE);
						XeToolTip8("Use incremental transformations for the the copies.");
						LResourcesGUI->IncrementalW = LW = XeWidget_ManagedNew("Incremental", XeToggleButton_class, LRadioMatrixW,  Args, ArgCnt);
						XeAddCallback(LW, XeNValueChangedCallback, _DCB_Incremental, (EPointer)PPanel);

						EXeStart;
						EXeSetArg(XeNShadowThickness, 0);
						EXeSetArg(XeNHighlightThickness, 0);
						EXeSetArg(XeNSpacingLeft, 100);EXeSetArg(XeNMarginRight, 0);EXeSetArg(XeNMarginTop, 0);
						EXeSetArg(XeNMarginBottom, 0);
						EXeSetArg(XeNSet, LInfo->Mode == E3dOVER_SHAPE);
						EXeSetArg(XeNResizableY, FALSE);
						XeToolTip8("Distribute the copies along a Spline or on the surface of a Mesh.");
						LResourcesGUI->OverShapeW = LW = XeWidget_ManagedNew("On object", XeToggleButton_class, LRadioMatrixW,  Args, ArgCnt);
						XeAddCallback(LW, XeNValueChangedCallback, _DCB_OverShape, (EPointer)PPanel);

						EXeStart;
						EXeSetArg(XeNMarginSizeX, 0);EXeSetArg(XeNSpacingLeft, 8);
						EXeSetArg(XeNShadowThickness, 0);EXeSetArg(XeNLabelType, XeIMAGE);EXeSetArg(XeNImageSizeYPtr, &Xe_IconSize);EXeSetImages2(_Xe_Theme->Image_Find);
						EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNAlignmentX, XeAlignBEGINNING);
						EXeSetArg(XeNResizableY, FALSE);
						XeToolTip8("Select Object (so you can see it).");
						if(LInfo->Mode) LResourcesGUI->ShapeShowW = LW = XeWidget_ManagedNew("Show", XePushButton_class, LRadioMatrixW,  Args, ArgCnt);
						else LResourcesGUI->ShapeShowW = LW = XeWidget_New("Show", XePushButton_class, LRadioMatrixW,  Args, ArgCnt);
						XeAddCallback(LW, XeNActivateCallback, _DCB_ShapeShow, (EPointer)PPanel);

						EXeStart;
						EXeSetArg(XeNMarginSizeX, 0);EXeSetArg(XeNShadowThickness, 0);EXeSetArg(XeNImageSizeYPtr, &Xe_IconSize);EXeSetArg(XeNLabelType, XeIMAGE);EXeSetImages2(_Xe_Theme->Image_Replace);
						EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNAlignmentX, XeAlignBEGINNING);
						EXeSetArg(XeNResizableY, FALSE);
						XeToolTip8("Pick new shape (Mesh or Spline) on which to distribute the copies.");
						if(LInfo->Mode) LResourcesGUI->ShapePickW = LW = XeWidget_ManagedNew("Pick", XePushButton_class, LRadioMatrixW,  Args, ArgCnt);
						else LResourcesGUI->ShapePickW = LW = XeWidget_New("Pick", XePushButton_class, LRadioMatrixW,  Args, ArgCnt);
						XeAddCallback(LW, XeNActivateCallback, _DCB_ShapePick, (EPointer)PPanel);



						EXeStart;
						EXeSetArg(XeNShadowThickness, 0);EXeSetArg(XeNHighlightThickness, 0);
						EXeSetArg(XeNSpacingLeft, 2);
						EXeSetArg(XeNSet, LInfo->OrientAlongObject);
						EXeSetArg(XeNResizableY, FALSE);
						XeToolTip8("Orient (in addition to positioning) each copym along the Spline or surface.");
						LResourcesGUI->OrientAlongObjectW = LW = XeWidget_ManagedNew("Orient along", XeToggleButton_class, LTopMatrixW,  Args, ArgCnt);
						XeAddCallback(LW, XeNValueChangedCallback, _DCB_OrientAlongObject, (EPointer)PPanel);

						EXeStart;
						EXeSetArg(XeNShadowThickness, 0);EXeSetArg(XeNHighlightThickness, 0);
						EXeSetArg(XeNSpacingLeft, 2);
						EXeSetArg(XeNSet, LInfo->VisibleOnly);
						EXeSetArg(XeNResizableY, FALSE);
						XeToolTip8("Only copy / instance visible Objects.");
						LResourcesGUI->VisibleOnlyW = LW = XeWidget_ManagedNew("Visible only", XeToggleButton_class, LTopMatrixW,  Args, ArgCnt);
						XeAddCallback(LW, XeNValueChangedCallback, _DCB_VisibleOnly, (EPointer)PPanel);

						EXeStart;
						EXeSetArg(XeNShadowThickness, 0);EXeSetArg(XeNHighlightThickness, 0);
						EXeSetArg(XeNSpacingLeft, 2);
						EXeSetArg(XeNSet, LInfo->MakeModelsVisible);
						EXeSetArg(XeNResizableY, FALSE);
						XeToolTip8("Make copied Models visible, even if the original was hidden.\nThis allows you to keep the original hidden, as you might not want to render it.");
						LResourcesGUI->MakeModelsVisibleW = LW = XeWidget_ManagedNew("Make Models visible", XeToggleButton_class, LTopMatrixW,  Args, ArgCnt);
						XeAddCallback(LW, XeNValueChangedCallback, _DCB_MakeModelsVisible, (EPointer)PPanel);

						{
							EXeStart;
							EXeSetArg(XeNOrientation, XeHORIZONTAL);
							EXeSetArg(XeNColumns, 3);
							EXeSetArg(XeNMarginSizeX, 2);EXeSetArg(XeNMarginSizeY, 4);
							EXeSetArg(XeNSpacingX, 2);
							EXeSetArg(XeNSelfResizePolicyY, XeGROW);
							EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNAlignmentX, XeAlignBEGINNING);
							EXeSetArg(XeNResizableY, FALSE);
							XeWidget  LMatrixW = XeWidget_ManagedNew("", XeMatrixWidget_class, LTopMatrixW,  Args, ArgCnt);


#define _TypeFlagButton(mType, mTypeFlag, mName, mTooltip)\
	EXeStart;\
	EXeSetArg(XeNShadowThickness, 0);EXeSetArg(XeNHighlightThickness, 0);\
	EXeSetArg(XeNSet, (LInfo->ObjectTypeFlags & mTypeFlag) != 0);\
	EXeSetArg(XeNResizableY, FALSE);\
	XeToolTip8(mTooltip);\
	LResourcesGUI->mName##W = LW = XeWidget_ManagedNew(#mName, XeToggleButton_class, LMatrixW,  Args, ArgCnt);\
	XeAddCallback(LW, XeNValueChangedCallback, _DCB_##mName, (EPointer)PPanel)

							_TypeFlagButton(Camera,   E3dItemCAMERA,   Cameras,   "Copy / instance Cameras.");
							_TypeFlagButton(Light,    E3dItemLIGHT,    Lights,    "Copy / instance Lights.");
							_TypeFlagButton(PointSet, E3dItemPOINTSET, PointSets, "Copy / instance PointSets.");
							_TypeFlagButton(Spline,   E3dItemSPLINE,   Splines,   "Copy / instance Splines.");
							_TypeFlagButton(Face,     E3dItemFACE,     Faces,     "Copy / instance Faces.");
							_TypeFlagButton(Meshe,    E3dItemMESH,     Meshes,    "Copy / instance Meshes.");
						}
					}

					{
						EXeStart;
						EXeSetArg(XeNOrientation, XeHORIZONTAL);
						EXeSetArg(XeNMarginSizeX, 2);EXeSetArg(XeNMarginSizeY, 5);
						EXeSetArg(XeNSpacingX, 0);EXeSetArg(XeNSpacingY, 0);
						EXeSetArg(XeNColumns, 3);
						EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNAlignmentX, XeAlignBEGINNING);
						EXeSetArg(XeNResizableY, FALSE);
						XeWidget  LMatrixW = XeWidget_ManagedNew("", XeMatrixWidget_class, LTopMatrixW,  Args, ArgCnt);
						LResourcesGUI->ScaleUBySplineMatrixW = LMatrixW;

						_ScaleBySplineGUI(U, LInfo, PPanel, LResourcesGUI, LMatrixW);
						_ScaleBySplineGUI(X, LInfo, PPanel, LResourcesGUI, LMatrixW);
						_ScaleBySplineGUI(Y, LInfo, PPanel, LResourcesGUI, LMatrixW);
					}



					EXeStart;
					EXeSetArg(XeNShadowThickness, 2);
					EXeSetArg(XeNShadowType, XeShadowETCHED_IN);
					EXeSetArg(XeNResizableY, FALSE);
					LResourcesGUI->Separator0W = XeWidget_ManagedNew("", XeSeparatorWidget_class, LTopMatrixW,  Args, ArgCnt);



					EXeStart;
					EXeSetArg(XeNMarginTop, 7);
					EXeSetArg(XeNTextFieldDigits, 6);EXeSetArg(XeNDecimalPoints, 3);
					EXeSetArg(XeNResizableY, FALSE);
					LIndentWs[LIndentN++] = LTraverseWs[LTraverseN++] = LResourcesGUI->ScalingXW = LW = XeScale_New_Coord(LTopMatrixW, "Scale X",  Args, ArgCnt);
					XeScale_SetValueAndRange_Coord(LW,  LTransformSet->Scaling.X,  -100.0, 100.0,  FALSE);
					XeAddCallback(LW, XeNValueChangedCallback, _DCB_ScalingX, (EPointer)PPanel);

					EXeStart;
					EXeSetArg(XeNTextFieldDigits, 6);EXeSetArg(XeNDecimalPoints, 3);
					EXeSetArg(XeNResizableY, FALSE);
					LIndentWs[LIndentN++] = LTraverseWs[LTraverseN++] = LResourcesGUI->ScalingYW = LW = XeScale_New_Coord(LTopMatrixW, "Scale Y",  Args, ArgCnt);
					XeScale_SetValueAndRange_Coord(LW,  LTransformSet->Scaling.Y,  -100.0, 100.0,  FALSE);
					XeAddCallback(LW, XeNValueChangedCallback, _DCB_ScalingY, (EPointer)PPanel);

					EXeStart;
					EXeSetArg(XeNTextFieldDigits, 6);EXeSetArg(XeNDecimalPoints, 3);
					EXeSetArg(XeNResizableY, FALSE);
					LIndentWs[LIndentN++] = LTraverseWs[LTraverseN++] = LResourcesGUI->ScalingZW = LW = XeScale_New_Coord(LTopMatrixW, "Scale Z",  Args, ArgCnt);
					XeScale_SetValueAndRange_Coord(LW,  LTransformSet->Scaling.Z,  -100.0, 100.0,  FALSE);
					XeAddCallback(LW, XeNValueChangedCallback, _DCB_ScalingZ, (EPointer)PPanel);



					EXeStart;
					EXeSetArg(XeNMarginTop, 7);
					EXeSetArg(XeNTextFieldDigits, 6);EXeSetArg(XeNDecimalPoints, 3);
					EXeSetArg(XeNResizableY, FALSE);
					LIndentWs[LIndentN++] = LTraverseWs[LTraverseN++] = LResourcesGUI->RotationXW = LW = XeScale_New_Coord(LTopMatrixW, "Rotate X",  Args, ArgCnt);
					XeScale_SetValueAndRange_Coord(LW,  LTransformSet->EulerRotation.Angles.X,  -180.0, 180.0,  FALSE);
					XeAddCallback(LW, XeNValueChangedCallback, _DCB_RotationX, (EPointer)PPanel);

					EXeStart;
					EXeSetArg(XeNTextFieldDigits, 6);EXeSetArg(XeNDecimalPoints, 3);
					EXeSetArg(XeNResizableY, FALSE);
					LIndentWs[LIndentN++] = LTraverseWs[LTraverseN++] = LResourcesGUI->RotationYW = LW = XeScale_New_Coord(LTopMatrixW, "Rotate Y",  Args, ArgCnt);
					XeScale_SetValueAndRange_Coord(LW,  LTransformSet->EulerRotation.Angles.Y,  -180.0, 180.0,  FALSE);
					XeAddCallback(LW, XeNValueChangedCallback, _DCB_RotationY, (EPointer)PPanel);

					EXeStart;
					EXeSetArg(XeNTextFieldDigits, 6);EXeSetArg(XeNDecimalPoints, 3);
					EXeSetArg(XeNResizableY, FALSE);
					LIndentWs[LIndentN++] = LTraverseWs[LTraverseN++] = LResourcesGUI->RotationZW = LW = XeScale_New_Coord(LTopMatrixW, "Rotate Z",  Args, ArgCnt);
					XeScale_SetValueAndRange_Coord(LW,  LTransformSet->EulerRotation.Angles.Z,  -180.0, 180.0,  FALSE);
					XeAddCallback(LW, XeNValueChangedCallback, _DCB_RotationZ, (EPointer)PPanel);



					EXeStart;
					EXeSetArg(XeNMarginTop, 7);
					EXeSetArg(XeNTextFieldDigits, 6);EXeSetArg(XeNDecimalPoints, 3);
					EXeSetArg(XeNResizableY, FALSE);
					LIndentWs[LIndentN++] = LTraverseWs[LTraverseN++] = LResourcesGUI->TranslationXW = LW = XeScale_New_Coord(LTopMatrixW, "Translate X",  Args, ArgCnt);
					XeScale_SetValueAndRanges_Coord(LW,  LTransformSet->Translation.X,  -1000.0, 1000.0,  -1.0, 1.0,  FALSE);
					XeAddCallback(LW, XeNValueChangedCallback, _DCB_TranslationX, (EPointer)PPanel);

					EXeStart;
					EXeSetArg(XeNTextFieldDigits, 6);EXeSetArg(XeNDecimalPoints, 3);
					EXeSetArg(XeNResizableY, FALSE);
					LIndentWs[LIndentN++] = LTraverseWs[LTraverseN++] = LResourcesGUI->TranslationYW = LW = XeScale_New_Coord(LTopMatrixW, "Translate Y",  Args, ArgCnt);
					XeScale_SetValueAndRanges_Coord(LW,  LTransformSet->Translation.Y,  -1000.0, 1000.0,  -1.0, 1.0,  FALSE);
					XeAddCallback(LW, XeNValueChangedCallback, _DCB_TranslationY, (EPointer)PPanel);

					EXeStart;
					EXeSetArg(XeNTextFieldDigits, 6);EXeSetArg(XeNDecimalPoints, 3);
					EXeSetArg(XeNResizableY, FALSE);
					LIndentWs[LIndentN++] = LTraverseWs[LTraverseN++] = LResourcesGUI->TranslationZW = LW = XeScale_New_Coord(LTopMatrixW, "Translate Z",  Args, ArgCnt);
					XeScale_SetValueAndRanges_Coord(LW,  LTransformSet->Translation.Z,  -1000.0, 1000.0,  -1.0, 1.0,  FALSE);
					XeAddCallback(LW, XeNValueChangedCallback, _DCB_TranslationZ, (EPointer)PPanel);


					E3dPathNormalGUI_New(&LResourcesGUI->PathNormalGUI, LTopMatrixW, (EPointer)PPanel, LInfo->PathNormalSource, &LInfo->PathNormal);

// Indent sliders
//
					XeWidgets_IndentFromLeft(LIndentWs, LIndentN, 0);

					XeTraversal_SetRing(LTraverseWs, LTraverseN);

					{
						EXeStart;
						EXeSetArg(XeNOrientation, XeHORIZONTAL);
						EXeSetArg(XeNMarginSizeX, 2);EXeSetArg(XeNMarginTop, 7);
						EXeSetArg(XeNSpacingX, 0);
						EXeSetArg(XeNSelfResizePolicyY, XeGROW);
						EXeSetArg(XeNResizableY, FALSE);
						XeWidget  LMatrixW = XeWidget_ManagedNew("", XeMatrixWidget_class, LTopMatrixW,  Args, ArgCnt);
						LResourcesGUI->OffsetMatrixW = LMatrixW;

						EXeStart;
						EXeSetArg(XeNMarginLeft, 2);
						EXeSetArg(XeNTextFieldDigits, 6);EXeSetArg(XeNDecimalPoints, 3);
						EXeSetArg(XeNResizableY, FALSE);
						LIndentWs[LIndentN++] = LTraverseWs[LTraverseN++] = LResourcesGUI->OffsetW = LW = XeScale_New_Coord(LMatrixW, "Offset",  Args, ArgCnt);
						XeScale_SetValueAndRanges_Coord(LW,  LInfo->Offset,  -100.0, 100.0,  -2.0, 2.0,  FALSE);
						XeAddCallback(LW, XeNValueChangedCallback, _DCB_Offset, (EPointer)PPanel);

						EXeStart;
						EXeSetArg(XeNShadowThickness, 0);
						EXeSetArg(XeNFontList, Xe_LabelFontList);
						EXeSetArg(XeNSpacingLeft, 2);
						EXeSetArg(XeNMarginSizeX, 0);EXeSetArg(XeNMarginSizeY, 0);
						EXeSetArg(XeNLabelType, XeIMAGE);
						EXeSetImages3(_Image_Key);
						EXeSetArg(XeNImageResizePolicy, XeSHRINK_X|XeGROW_X|XeSHRINK_Y|XeGROW_Y);
						EXeSetArg(XeNImageSizeYPtr, &Xe_IconSize);
						EXeSetArg(XeNResizableX, FALSE);
						XeToolTip8("Add keyframe");
						LW = XeWidget_ManagedNew("KeyFrameBW", XePushButton_class, LMatrixW,  Args, ArgCnt);
						XeAddCallback(LW, XeNActivateCallback, _CB_OffsetAddKeyFrame, (EPointer)PPanel);
					}

					_SetGUI(LInfo, LResourcesGUI);
				}
				else
				{
					E3dTransformSet*  LTransformSet = &LInfo->TransformIncrement;

					LResourcesGUI = (E3dResourcesGUI*)(LDrawer->ResourcesGUI);

					_SetGUI(LInfo, LResourcesGUI);

					XeToggleButton_SetState(LResourcesGUI->LockW, (LInfo->Flags & (EComponentANIMATABLE|EComponentRESPOND_TO_INPUT_CHANGE)) != (EComponentANIMATABLE|EComponentRESPOND_TO_INPUT_CHANGE), FALSE);

					XeToggleButton_SetState(LResourcesGUI->InstanceW,      LInfo->Instance,                                      FALSE);
					XeToggleButton_SetState(LResourcesGUI->CopyMaterialsW, (EBool)((LInfo->CloneFlags & E3dCopyMATERIALS) != 0), FALSE);

					XeToggleButton_SetState(LResourcesGUI->IncrementalW, LInfo->Mode == E3dINCREMENTAL, FALSE);
					XeToggleButton_SetState(LResourcesGUI->OverShapeW,   LInfo->Mode == E3dOVER_SHAPE,  FALSE);

					XeToggleButton_SetState(LResourcesGUI->OrientAlongObjectW, LInfo->OrientAlongObject, FALSE);
					XeToggleButton_SetState(LResourcesGUI->VisibleOnlyW,       LInfo->VisibleOnly,       FALSE);
					XeToggleButton_SetState(LResourcesGUI->MakeModelsVisibleW, LInfo->MakeModelsVisible, FALSE);

					XeToggleButton_SetState(LResourcesGUI->CamerasW,   (LInfo->ObjectTypeFlags & E3dItemCAMERA)   != 0, FALSE);
					XeToggleButton_SetState(LResourcesGUI->LightsW,    (LInfo->ObjectTypeFlags & E3dItemLIGHT)    != 0, FALSE);
					XeToggleButton_SetState(LResourcesGUI->PointSetsW, (LInfo->ObjectTypeFlags & E3dItemPOINTSET) != 0, FALSE);
					XeToggleButton_SetState(LResourcesGUI->SplinesW,   (LInfo->ObjectTypeFlags & E3dItemSPLINE)   != 0, FALSE);
					XeToggleButton_SetState(LResourcesGUI->FacesW,     (LInfo->ObjectTypeFlags & E3dItemFACE)     != 0, FALSE);
					XeToggleButton_SetState(LResourcesGUI->MeshesW,    (LInfo->ObjectTypeFlags & E3dItemMESH)     != 0, FALSE);

					XeToggleButton_SetState(LResourcesGUI->ScaleBySplineWs[E3dSCALESPL_X], LInfo->ScaleXBySpline.Active, FALSE);
					XeToggleButton_SetState(LResourcesGUI->ScaleBySplineWs[E3dSCALESPL_Y], LInfo->ScaleYBySpline.Active, FALSE);

					XeToggleButton_SetState(LResourcesGUI->ScaleBySplineWs[E3dSCALESPL_U], LInfo->ScaleUBySpline.Active, FALSE);

					XeScale_SetValue(LResourcesGUI->DuplicatesScaleW, LInfo->NumOfDuplicates, FALSE);

					XeScale_SetValue_Coord(LResourcesGUI->TranslationXW, LTransformSet->Translation.X, FALSE);
					XeScale_SetValue_Coord(LResourcesGUI->TranslationYW, LTransformSet->Translation.Y, FALSE);
					XeScale_SetValue_Coord(LResourcesGUI->TranslationZW, LTransformSet->Translation.Z, FALSE);

					XeScale_SetValue_Coord(LResourcesGUI->RotationXW, LTransformSet->EulerRotation.Angles.X, FALSE);
					XeScale_SetValue_Coord(LResourcesGUI->RotationYW, LTransformSet->EulerRotation.Angles.Y, FALSE);
					XeScale_SetValue_Coord(LResourcesGUI->RotationZW, LTransformSet->EulerRotation.Angles.Z, FALSE);

					XeScale_SetValue_Coord(LResourcesGUI->ScalingXW, LTransformSet->Scaling.X, FALSE);
					XeScale_SetValue_Coord(LResourcesGUI->ScalingYW, LTransformSet->Scaling.Y, FALSE);
					XeScale_SetValue_Coord(LResourcesGUI->ScalingZW, LTransformSet->Scaling.Z, FALSE);

					XeScale_SetValue_Coord(LResourcesGUI->OffsetW, LInfo->Offset, FALSE);

					_PathNormalGUIUpdate(&LResourcesGUI->PathNormalGUI, LInfo->PathNormalSource, &LInfo->PathNormal);
				}
			}
		break;
	}
}


//================================================
// Pop up the "Duplicate with repeat" dialog box
//================================================
void _DuplicateWithRepeat(const int PMode)
{
	switch(PMode)
	{
		case E3dINCREMENTAL:
			{
				EListAI_s(, E3dNodePtr, LSelectedNodes);E3dScene_GetSelectedNodes(E3d_Scene, E3dNodeSelNODE|E3dNodeSelBRANCH_ROOT, FALSE,  &LSelectedNodes);if(LSelectedNodes.Count > 0)
				{
					E3dNode*  LRootNodeToDuplicate = LSelectedNodes.Items[0];
					E3dNode*  LUberRootNode;

					{
						char*  LNewRootName = LRootNodeToDuplicate->Name ? EMalloc(strlen(LRootNodeToDuplicate->Name) + 12) : EMalloc(12);

						E_NewCAssignNoChkNoFree(E3dModel_class, LUberRootNode);
						sprintf(LNewRootName, "%s-copies", LRootNodeToDuplicate->Name);
						EObject_SetName(LUberRootNode, EStrDup(LNewRootName), FALSE);
					}

					EList_FreeA_s(LSelectedNodes);

					E3dDuplicateWithRepeatInfo*  LInfo = (E3dDuplicateWithRepeatInfo*)E3dNodeComponent_Add(LUberRootNode, _DuplicateWithRepeatComponentClass);

					LInfo->Mode = PMode;

					E_AssignToMember(LRootNodeToDuplicate, LInfo,NodeToDuplicate);
					E3dNode_Edit(LUberRootNode, _DuplicateWithRepeatComponentClass);

					_DuplicateRep(E3d_Scene, LInfo->NodeToDuplicate, LUberRootNode, LInfo);

// Just in case _DuplicateRep failed
//
					E_Delete(LUberRootNode);
				}
				else { E3dp_PrintMessage(ELogWARNING, 5000, "Select Model(s) to duplicate");return; }
			}
		break;

		case E3dOVER_SHAPE:
			{
				_RequestShapePick();
			}
		break;
	}
}


// E3dFINISHME
static EChangeFlags _Update(E3dNode* PChangedBranchRootNode, E3dNode* PNode, E3dNodeComponent* PInfo, const EChangeFlags PChanged, const EBool PCheckOnly)
{
	E3dInfo*      LInfo = (E3dInfo*)PInfo;
	EChangeFlags  LTriggeredChanges = 0;

//assert(PCheckOnly);

//return(0);

	if(LInfo == NULL) return(0);  // This prevents a crash when trying to save a Scene with a DuplicateWithRepeat Node/Branch that was manually copied

// Check only
//
	if(PCheckOnly)
	{
		if(LInfo->Shape) E3dBaseObject_AddOutput(LInfo->Shape, &LInfo->InputInterface);

		if(LInfo->ScaleUBySpline.OrigSpline) E3dBaseObject_AddOutput((E3dBaseObject*)LInfo->ScaleUBySpline.OrigSpline, &LInfo->InputInterface);
		if(LInfo->ScaleXBySpline.OrigSpline) E3dBaseObject_AddOutput((E3dBaseObject*)LInfo->ScaleXBySpline.OrigSpline, &LInfo->InputInterface);
		if(LInfo->ScaleYBySpline.OrigSpline) E3dBaseObject_AddOutput((E3dBaseObject*)LInfo->ScaleYBySpline.OrigSpline, &LInfo->InputInterface);
	}
	else
	{
//{ char  s[256];printf("%s() %s:%d  [%p:%s]  [%p:%s]  %08x [%s] %d\n", __FUNCTION__, __FILE__, __LINE__, PChangedBranchRootNode, PChangedBranchRootNode ? PChangedBranchRootNode->Name : "", PNode, PNode ? PNode->Name : "", PChanged, E3dSceneChangeFlags_ToString(PChanged, EfCOLOR,  s), PCheckOnly);fflush(stdout); }
//		if(!LInfo->Lock)

//printf("%s() %s:%d  [%p:%s/%p:%s] Offset %f\n", __FUNCTION__, __FILE__, __LINE__, PNode, PNode->Name, LInfo, LInfo->Class->Name, LInfo->Offset);fflush(stdout);
		if(LInfo->Flags & EComponentRESPOND_TO_INPUT_CHANGE)
		{
			_DuplicateRep(E3d_Scene, LInfo->NodeToDuplicate, LInfo->Node, LInfo);

			const E3dNode*  LNode = LInfo->Node;const E3dNode*  LNodeAfterBranch = E3dNode_BranchGetNodeAfter(LNode);for(;LNode != LNodeAfterBranch;LNode = LNode->Next)
			{
				LTriggeredChanges |= E3dModel_GetChangeFlagsByObjects((E3dModel*)LNode);
			}
			LTriggeredChanges |= E3dChgHIERARCHY|E3dChgNodeTRANSFORM;
		}
		else
		{
			LInfo->ChangeRequestsCached |= PChanged;
		}
	}

//{ char  s[8192];printf("%s() %s:%d  %08x %s\n", __FUNCTION__, __FILE__, __LINE__, LTriggeredChanges, E3dSceneChangeFlags_ToString(LTriggeredChanges, EfCOLOR,  s));fflush(stdout); }

	return(LTriggeredChanges);
}


//========================================
// Duplicate menu callback
//========================================
static void _MCB(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	switch((EPointerInt)PClientData)
	{
		case CR_DUPLICATE_WITH_REPEAT:
			_DuplicateWithRepeat(E3dINCREMENTAL);
		break;

		case CR_DUPLICATE_WITH_REPEAT_OVER_SHAPE:
			_DuplicateWithRepeat(E3dOVER_SHAPE);
		break;
	}
}


//========================================
// Key callback
//========================================
static void _KCB(const EKeyCode PKey, const unsigned int PModifiers, const int PKeyEventType, EPointer PClientData)
{
	switch((EPointerInt)PClientData)
	{
		case CR_DUPLICATE_WITH_REPEAT:
			_DuplicateWithRepeat(E3dINCREMENTAL);
		break;

		case CR_DUPLICATE_WITH_REPEAT_OVER_SHAPE:
			_DuplicateWithRepeat(E3dOVER_SHAPE);
		break;
	}
}


//========================================
// Entry point of the plugin
//========================================
EBool _DuplicateWithRepeatInit(void)
{
	{
		E3dNodeComponentClass  LNodeComponentClass=
		{
			.Name=          "Duplicate with repeat",
			.StructSize=    sizeof(E3dDuplicateWithRepeatInfo),
			.Resources=     EListS_s(_DuplicateWithRepeatResources_a),

			.Init=          _Init,
			.Free=          _Free,
			.Update=        _Update,
			.GUI=           _GUI,
		};
		_DuplicateWithRepeatComponentClass = E3dNodeComponent_ClassRegister(&LNodeComponentClass);
	}

	if(_DuplicateWithRepeatComponentClass)
	{
		E_Assign(_Image_DuplicateBig, _DuplicateWithRepeatComponentClass->Icon);

		const EKeyModifiers  LCtrlMask = Xe_CommandKeyForCtrl ? E_CommandModifierMask : EControlMask;const char*  LCtrlName = Xe_CommandKeyForCtrl ? "Cmd" : "Ctrl";char                 LAccText[256];
		sprintf(LAccText, "Shift+%s+D", LCtrlName);_MenuButtons[_MenuButtons_Count++] = XePushButton_Add(Xe_App, "Menu->Clone", "With repeat", '\0', LAccText, NULL, FALSE, NULL, _MCB, (EPointer)CR_DUPLICATE_WITH_REPEAT);
		E_KeySetCallback("Duplicate: with repeat", 'D', LCtrlMask|EShiftMask, EKeyPRESS, _KCB, (EPointer)CR_DUPLICATE_WITH_REPEAT);

		_MenuButtons[_MenuButtons_Count++] = XePushButton_Add(Xe_App, "Menu->Clone", "Over shape", '\0', "Shift+Alt+D", NULL, FALSE, NULL, _MCB, (EPointer)CR_DUPLICATE_WITH_REPEAT_OVER_SHAPE);
		E_KeySetCallback("Duplicate: over shape", 'D', EAltMask|EShiftMask,      EKeyPRESS, _KCB, (EPointer)CR_DUPLICATE_WITH_REPEAT_OVER_SHAPE);
		return(TRUE);
	}
	return(FALSE);
}


void _DuplicateWithRepeatFree(void)
{
	int  LI = 0;for(;LI < _MenuButtons_Count;LI++) { XeItemFree(_MenuButtons[LI]); } _MenuButtons_Count = 0;

	const EKeyModifiers  LCtrlMask = Xe_CommandKeyForCtrl ? E_CommandModifierMask : EControlMask;
	E_KeyRemoveCallback('D', LCtrlMask|EShiftMask,  EKeyPRESS, _KCB, (EPointer)CR_DUPLICATE_WITH_REPEAT);
	E_KeyRemoveCallback('D', EAltMask|EShiftMask,   EKeyPRESS, _KCB, (EPointer)CR_DUPLICATE_WITH_REPEAT_OVER_SHAPE);

	if(_DuplicateWithRepeatComponentClass) { E3dNodeComponent_ClassDeactivate(_DuplicateWithRepeatComponentClass);_DuplicateWithRepeatComponentClass = NULL; }

	EpIconFree(DuplicateBig);
}
