/*======================================================================================================*/
/* 3DPanel												*/
/*													*/
/* - Transform panel											*/
/*													*/
/* AUTHOR:	Gabor Nagy										*/
/* DATE:	1996-Dec-24 23:30:05									*/
/*													*/
/* EQUINOX-3D(TM), 3DPanel(TM) and 3DLib(TM) Copyright (C) 1995 By Gabor Nagy. All rights reserved.	*/
/*======================================================================================================*/
#include <float.h>
#include <math.h>
#include <stdio.h>

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


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

#include <Color/XColor.h>

#include <EGUI/Dialog.h>


#include <E3D/E3D.h>

#include <E3D/Matrix.h>

#include <E3D/Panel.h>

#include <E3D/Pick.h>

#include <E3D/StatusPanel.h>

#include <E3D/TagOps.h>

#include <E3D/XGUI.h>



//------------------------------------------------
// From Display.c
//------------------------------------------------
extern E3dShadowMap*	_ShadowMap;


extern EpCImage		Image_XKey;
extern EpCImage		Image_CKey;
extern EpCImage		Image_VKey;



int		E3dp_TransformTarget=E3dpTT_OBJECT, E3dp_TransformMode=E3dpTM_VIEW;
int		E3dp_ScaleFlags=0, E3dp_RotateFlags=0, E3dp_TranslateFlags=0;

EBool		E3dp_Transforming=FALSE;		// Scaling, Rotating, Translating etc. selected models (keep buffer of rendered unselected models, grid, axis...)
EBool		E3dp_TransformObjectsSensitive=FALSE;


Widget		E3dp_TransformPanelW=NULL,
		E3dp_TransformTargetW=NULL,
		E3dp_TransformModeW=NULL,
		E3dp_ScaleXW=(Widget)NULL, E3dp_ScaleYW=(Widget)NULL, E3dp_ScaleZW=(Widget)NULL,
		E3dp_RotateXW=(Widget)NULL, E3dp_RotateYW=(Widget)NULL, E3dp_RotateZW=(Widget)NULL,
		E3dp_TranslateXW=(Widget)NULL, E3dp_TranslateYW=(Widget)NULL, E3dp_TranslateZW=(Widget)NULL,
		_TTB_ObjectW=NULL, _TTB_TagW=NULL;



static Widget	_ModelNameLabelW=NULL,
		E3dp_ScaleXBW=NULL, E3dp_ScaleYBW=NULL, E3dp_ScaleZBW=NULL,
		E3dp_RotateXBW=NULL, E3dp_RotateYBW=NULL, E3dp_RotateZBW=NULL, 
		E3dp_TranslateXBW=NULL, E3dp_TranslateYBW=NULL, E3dp_TranslateZBW=NULL;


int		E3dp_PrevPtrX=-1, E3dp_PrevPtrY=-1;

int		E3dp_BasePtrX=-1, E3dp_BasePtrY=-1;

E3d3DPosition	E3dp_Translation =	{ 0.0, 0.0, 0.0 };
E3d3DPosition	E3dp_Scaling =		{ 1.0, 1.0, 1.0 };
E3dRotation	E3dp_Rotation =		{ 0.0, 0.0, 0.0 };


EOperator*	E3d_CurrentOperator=NULL;




//----------------------------------------
// Single-point moving
//----------------------------------------
int		E3dp_OldPointFlags=0;
E3dGeometry*	E3dp_ActiveGeometry=NULL;
E3dSpline*	E3dp_ActiveSpline=NULL;			// If E3dp_ActiveGeometry is a Face, this will point to the active contour
E3dVertex* 	E3dp_ActiveVertex=NULL;

int		E3dp_BezCVActiveSubCV=E3dBEZ_PREVIOUS;	// Which component of the BezierCV to move
E3dBezierCV*	E3dp_ActiveBezierCV=NULL;
E3dSplineCV*	E3dp_ActiveSplineCV=NULL;

E3dMatrix	E3dp_WorldToLocalMatrix;
E3dMatrix	E3dp_LocalToWorldMatrix;


E3d3DPosition	E3dp_ActivePointOriginalPos, E3dp_ActivePointMoveOffset;

// The coefficients of the plane used to move a point
//
E3dCoordinate	E3dp_PointMovePlaneCDX,			// -A
		E3dp_PointMovePlaneCDY,			// -B
		E3dp_PointMovePlaneCDZ,			// -C
		E3dp_PointMovePlaneDCoefficient;	// D

E3dVertex 	E3dp_ActiveVertexOrig;
E3dBezierCV	E3dp_ActiveBezierCVOrig;
E3dSplineCV	E3dp_ActiveSplineCVOrig;





// Models being transformed
//
int		E3dp_NumOfTransformedModels=0;
E3dModel**	E3dp_TransformedModels=NULL;

// Root Models of hierarchies that have Models being transformed
//
unsigned int	E3dp_NumOfTransformedModelRoots=0;
E3dModel**	E3dp_TransformedModelRoots=NULL;

// Geometries with Vertices to be transformed (e.g. tagged Vertices)
//
unsigned int		E3dp_NumOfShapeSnapshots=0;
E3dShapeSnapshot*	E3dp_ShapeSnapshots=NULL;

static EBool	E3dp_PushOperatorsForUndo=FALSE;
//static EBool	E3dp_PushActivePointForUndo=FALSE;


static Arg	Args[256];
static Cardinal	ArgCnt;


enum
{
 E3dCR_SCALE=1,
 E3dCR_ROTATE,
 E3dCR_TRANSLATE
};


//----------------------------------------
// Function prototypes
//----------------------------------------
void		E3dp_SetTransScaleStates();
unsigned int	E3dOp_TranslateTags(EOperator* LOperatorIn, int LOperation);




//----------------------------------------
// Macro functions
//----------------------------------------

#define E3dM_SetIconL(mImage)\
{\
 EXtSetArg(XeNxSpacing, 5);\
 EXtSetArg(XeNlabelType, XePIXMAP_STRING_HORIZONTAL);\
 EXtSetArg(XeNlabelPixmap, EGUI_VaCImageToPixmap(&(mImage), EguiNbackground, &(E3dp_Prefs.MenuRGBAiColor), NULL));\
}


#define _M_SetTransformTargetString()\
 switch(E3dp_TransformTarget)\
 {\
  caseE3dpTT_OBJECT():	LStrP="OBJECT";break;\
  case E3dpTT_TAG:	LStrP="TAGGED POINTS";break;\
  case E3dpTT_CENTER:	LStrP="CENTER";break;\
  case E3dpTT_TEXTURE:	LStrP="TEXTURE";break;\
  default:		LStrP="";break;\
 }





//================================================================
// Print proper transformation message on the StatusPanel
//================================================================
static void _SetTransformingMessage()
{
 char*	LStrP;

 _M_SetTransformTargetString();

 if(E3dp_ScaleFlags)
 {
  E3dp_PrintMessage(0, 0, "Scaling %s    LMB: along X   MMB: along Y   RMB: along Z", LStrP);
 }
 else if(E3dp_RotateFlags)
 {
  E3dp_PrintMessage(0, 0, "Rotating %s    LMB: around X   MMB: around Y   RMB: around Z", LStrP);
 }
 else if(E3dp_TranslateFlags)
 {
  switch(E3dp_TransformMode)
  {
   case E3dpTM_LOCAL:
    E3dp_PrintMessage(0, 0, "Translate %s   LMB: along LOCAL X   MMB: along LOCAL Y   RMB: along LOCAL Z", LStrP);
   break;

   case E3dpTM_GLOBAL:
    E3dp_PrintMessage(0, 0, "Translate %s   LMB: along X   GLOBAL MMB: along Y   GLOBAL RMB: along GLOBAL Z", LStrP);
   break;

   case E3dpTM_VIEW:
    E3dp_PrintMessage(0, 0, "Translate %s   LMB: Along view", LStrP);
   break;
  }
 }
}


//========================================
// Key callbacks
//========================================
static void E3dpKCB_Transform(unsigned short LKey, unsigned int LModifiers, int LKeyEventType, EPointer LClientData)
{
 E3dp_ResetWorkModes();
 switch((int)LClientData)
 {
  case E3dCR_SCALE:
   if(E3dp_ScaleFlags!=(E3dDO_X|E3dDO_Y|E3dDO_Z))
   {
    E3dp_ScaleFlags=E3dDO_X|E3dDO_Y|E3dDO_Z;E3dp_RotateFlags=0;E3dp_TranslateFlags=0;
    E3dp_Transforming=TRUE;
    _SetTransformingMessage();
   }
   else { E3dp_ScaleFlags=0;E3dp_Transforming=FALSE;E3dp_PrintMessage(0, 0, " "); }

   E3dp_SetTransScaleStates();
   E3dp_Refresh3DWindows(E3dDF_WIREFRAME, E3dVM_ALL);
  break;

  case E3dCR_ROTATE:
   if(E3dp_RotateFlags!=(E3dDO_X|E3dDO_Y|E3dDO_Z))
   {
    E3dp_RotateFlags=E3dDO_X|E3dDO_Y|E3dDO_Z;E3dp_ScaleFlags=0;E3dp_TranslateFlags=0;
    E3dp_Transforming=TRUE;
    _SetTransformingMessage();
   }
   else { E3dp_RotateFlags=0;E3dp_Transforming=FALSE;E3dp_PrintMessage(0, 0, " "); }

   E3dp_SetTransScaleStates();
   E3dp_Refresh3DWindows(E3dDF_WIREFRAME, E3dVM_ALL);
  break;

  case E3dCR_TRANSLATE:
   if(E3dp_TranslateFlags!=(E3dDO_X|E3dDO_Y|E3dDO_Z))
   {
    E3dp_TranslateFlags=E3dDO_X|E3dDO_Y|E3dDO_Z;E3dp_ScaleFlags=0;E3dp_RotateFlags=0;
    E3dp_Transforming=TRUE;
    _SetTransformingMessage();
   }
   else { E3dp_TranslateFlags=0;E3dp_Transforming=FALSE;E3dp_PrintMessage(0, 0, " "); }

   E3dp_SetTransScaleStates();
   E3dp_Refresh3DWindows(E3dDF_WIREFRAME, E3dVM_ALL);
  break;
 }
}


//========================================
// Set transformation target
//========================================
void E3dpCB_SetTransformTarget(Widget LW, XtPointer LClientData, XtPointer LCallData)
{
 if(((XeToggleButtonCallbackStruct*)LCallData)->set)
 {
  E3dp_TransformTarget=(int)LClientData;

  if(E3dp_Transforming) _SetTransformingMessage();
 }
}

//========================================
// Set transformation mode
//========================================
void E3dpCB_SetTransformMode(Widget LW, XtPointer LClientData, XtPointer LCallData)
{
 if(((XeToggleButtonCallbackStruct*)LCallData)->set)
 {
  E3dp_TransformMode=(int)LClientData;

  if(E3dp_Transforming) _SetTransformingMessage();
 }
}


//========================================
// Transform-target button
//========================================
static Widget _CreateTransTargetButton(char* LName, Widget LParentW, int LCBData, char* LToolTip)
{
 Widget		LW;
 XeString	LXeStr;

// EguiVisual*	LVisual;

 EXtStart;
 EXtSetArg(XeNgapWidth, 1);EXtSetArg(XeNshadowThickness, 2);
 EXtSetArg(XeNbackground, EGUI_ButtonColor);
 EXtSetArg(XeNhighlightThickness, 0);
// EXtSetArg(XeNindicatorOn, False);
 EXtSetArg(XeNbuttonType, XeBUTTONTYPE_LED);
 EXtSetArg(XeNfontList, EGUI_LabelFontList);
 EXtSetArg(XeNactivateOnArm, True);

 if((LXeStr=XeStringVaCreate(True, XeSTRING_COMPONENT_CHARSET, "tag1", XeSTRING_COMPONENT_ALIGN, XeSTRING_ALIGN_LEFT, XeSTRING_COMPONENT_TEXT, LToolTip, XeSTRING_COMPONENT_UNKNOWN))!=NULL)
 {
  EXtSetArg(XeNtipBackground, Ec_XGetRGBAiColor(E3dp_Display, DefaultColormap(E3dp_Display, E3dp_Screen), &EGUI_ToolTipBackgroundRGBAiColor));
  EXtSetArg(XeNtipFontList, EGUI_LabelFontList);
  EXtSetArg(XeNtipString, LXeStr);
 }

 if(E3dp_TransformTarget==LCBData) EXtSetArg(XeNset, True);
 LW=XtCreateManagedWidget(LName, xeToggleButtonWidgetClass, LParentW, Args, ArgCnt);
 if(LXeStr) XeStringFree(LXeStr);
 XtAddCallback(LW, XeNvalueChangedCallback, E3dpCB_SetTransformTarget, (XtPointer)LCBData);

 return(LW);
}


//========================================
// Transform-mode button
//========================================
static Widget _CreateTransModeButton(char* LName, Widget LParentW, int LCBData, char* LToolTip)
{
 Widget		LW;
 XeString	LXeStr;
// EguiVisual*	LVisual=EGUI_TruecolorVisual;

 EXtStart;
/*
 if(LVisual)
 {
  EXtSetArg(XeNvisualInfo, LVisual->X_VisualInfo);
  EXtSetArg(XeNcolormap, LVisual->X_Colormap);
 }
*/
 EXtSetArg(XeNgapWidth, 1);EXtSetArg(XeNshadowThickness, 2);
 EXtSetArg(XeNbackground, EGUI_ButtonColor);
 EXtSetArg(XeNhighlightThickness, 0);
 EXtSetArg(XeNbuttonType, XeBUTTONTYPE_LED);
 EXtSetArg(XeNfontList, EGUI_LabelFontList);
 EXtSetArg(XeNactivateOnArm, True);
 if(E3dp_TransformMode==LCBData) EXtSetArg(XeNset, True);
 if((LXeStr=XeStringVaCreate(True, XeSTRING_COMPONENT_CHARSET, "tag1", XeSTRING_COMPONENT_ALIGN, XeSTRING_ALIGN_LEFT, XeSTRING_COMPONENT_TEXT, LToolTip, XeSTRING_COMPONENT_UNKNOWN))!=NULL)
 {
  EXtSetArg(XeNtipBackground, Ec_XGetRGBAiColor(E3dp_Display, DefaultColormap(E3dp_Display, E3dp_Screen), &EGUI_ToolTipBackgroundRGBAiColor));
  EXtSetArg(XeNtipFontList, EGUI_LabelFontList);
  EXtSetArg(XeNtipString, LXeStr);
 }

 LW=XtCreateManagedWidget(LName, xeToggleButtonWidgetClass, LParentW, Args, ArgCnt);
 if(LXeStr) XeStringFree(LXeStr);
 XtAddCallback(LW, XeNvalueChangedCallback, E3dpCB_SetTransformMode, (XtPointer)LCBData);

 return(LW);
}


//========================================
// Set transformation mode
//========================================
void EH_TransMode(Widget LW, XtPointer LClientData, XEvent* LXEv, Boolean* LCdr)
{
 switch(LXEv->type)
 {
  case ButtonPress:
   switch(LXEv->xbutton.button)
   {
    case Button1:
     E3dp_TranslateFlags=(int)LClientData;
    break;
   }
  break;
 }
}


//================================================
// Set Translation of the selected model(s)
//================================================
void E3dp_SetTranslation(E3dModel* LModel, double LTransX, double LTransY, double LTransZ, int LFlags, EBool LDoInWorld, EBool LDoBranch)
{
 if(LDoInWorld)
 {
  if(LFlags&E3dDO_X) LModel->LocalToWorldMatrix[M30]=LTransX;
  if(LFlags&E3dDO_Y) LModel->LocalToWorldMatrix[M31]=LTransY;
  if(LFlags&E3dDO_Z) LModel->LocalToWorldMatrix[M32]=LTransZ;
//  E3d_ModelHrcUpdateTransformsFromMatrices();
 }
 else
 {
  if(LFlags&E3dDO_X) LModel->Translation.X=LTransX;
  if(LFlags&E3dDO_Y) LModel->Translation.Y=LTransY;
  if(LFlags&E3dDO_Z) LModel->Translation.Z=LTransZ;
 }
 switch(LModel->Type)
 {
  case E3dMDL_LIGHT:
   if(LModel->Info)
   {
    E3dLightInfo*	LLightInfo=(E3dLightInfo*)(LModel->Info[0]);
    E3dLight*		LLight=LLightInfo->Light;

    if(LLight)
    {
     LLight->Position.X=LModel->Translation.X;
     LLight->Position.Y=LModel->Translation.Y;
     LLight->Position.Z=LModel->Translation.Z;
     E3d_SceneLightsRefreshGL(E3d_Scene);
     E3dp_Redraw3DWindows(E3dDF_ALL, E3dVM_ALL);
    }
   }
  break;
 }
}


//================================================================================
// Update scaling, rotation and translation Scales and selected Object list
//================================================================================
void E3dp_TransformPanelUpdate(int LScaleFlags, int LRotFlags, int LTransFlags, EBool LSelectionChanged)
{
 E3dModel**	LModels=NULL;
 E3dModel*	LModel=NULL;
 char*		LNonameStr;
 XeString	LXeStr;
 float		LScaleX=1.0, LScaleY=1.0, LScaleZ=1.0;
 float		LRotateX=0.0, LRotateY=0.0, LRotateZ=0.0;
 float		LTranslateX=0.0, LTranslateY=0.0, LTranslateZ=0.0;
 unsigned int	LNumOfSelectedModels, LC, LN;


 if((LNumOfSelectedModels=E3d_SceneGetSelectedModels(E3d_Scene, &LModels, TRUE))>0)
// if(E3d_SceneGetSingleSelection(E3d_Scene, &LItem, E3dITEM_MODEL)!=E3dITEM_NONE)
 {
  char	LTmpStr[4096];
  int	LMaxLines=42;

  if(LSelectionChanged)
  {
   LTmpStr[0]=0;
   LNonameStr=Lng_Translate("<no name>");

   if(LNumOfSelectedModels<=LMaxLines)
   {
    for(LC=0;LC<LNumOfSelectedModels;LC++)
    {
     if(LModels[LC]->Name) strcat(LTmpStr, LModels[LC]->Name);
     else strcat(LTmpStr, LNonameStr);
     if(LC<(LMaxLines-1)) strcat(LTmpStr, "\n");
    }
    for(;LC<(LMaxLines-1);LC++) strcat(LTmpStr, "\n");
   }
   else
   {
    LN=LMaxLines;
    for(LC=0;LC<LN;LC++)
    {
     if(LModels[LC]->Name) strcat(LTmpStr, LModels[LC]->Name);
     else strcat(LTmpStr, LNonameStr);
     if(LC<(LMaxLines-1)) strcat(LTmpStr, "\n");
    }
    strcat(LTmpStr, "  ->");
   }

   LXeStr=XeStringVaCreate(True, XeSTRING_COMPONENT_CHARSET, "tag1",
	 XeSTRING_COMPONENT_ALIGN, XeSTRING_ALIGN_LEFT,
	 XeSTRING_COMPONENT_TEXT, LTmpStr,
//	XeSTRING_COMPONENT_COLOR_NAME, "blue", XeSTRING_COMPONENT_TEXT, "http://www.equinox3d.com",
	 XeSTRING_COMPONENT_UNKNOWN);

   if(LXeStr)
   {
    XtVaSetValues(_ModelNameLabelW, XeNlabelString, LXeStr, NULL);

    XeStringFree(LXeStr);
   }
  }

  LModel=LModels[0];

  switch(E3dp_TransformTarget)
  {
   caseE3dpTT_OBJECT():
    LScaleX=LModel->Scaling.X;LScaleY=LModel->Scaling.Y;LScaleZ=LModel->Scaling.Z;
    LRotateX=LModel->Rotation.X;LRotateY=LModel->Rotation.Y;LRotateZ=LModel->Rotation.Z;
    LTranslateX=LModel->Translation.X;LTranslateY=LModel->Translation.Y;LTranslateZ=LModel->Translation.Z;
   break;
  }
  if(LSelectionChanged&&(!E3dp_TransformObjectsSensitive))
  {
   if(E3dp_ScaleXW!=(Widget)NULL) XtVaSetValues(E3dp_ScaleXW, XeNsensitive, True, NULL);
   if(E3dp_ScaleYW!=(Widget)NULL) XtVaSetValues(E3dp_ScaleYW, XeNsensitive, True, NULL);
   if(E3dp_ScaleZW!=(Widget)NULL) XtVaSetValues(E3dp_ScaleZW, XeNsensitive, True, NULL);
   if(E3dp_RotateXW!=(Widget)NULL) XtVaSetValues(E3dp_RotateXW, XeNsensitive, True, NULL);
   if(E3dp_RotateYW!=(Widget)NULL) XtVaSetValues(E3dp_RotateYW, XeNsensitive, True, NULL);
   if(E3dp_RotateZW!=(Widget)NULL) XtVaSetValues(E3dp_RotateZW, XeNsensitive, True, NULL);
   if(E3dp_TranslateXW!=(Widget)NULL) XtVaSetValues(E3dp_TranslateXW, XeNsensitive, True, NULL);
   if(E3dp_TranslateYW!=(Widget)NULL) XtVaSetValues(E3dp_TranslateYW, XeNsensitive, True, NULL);
   if(E3dp_TranslateZW!=(Widget)NULL) XtVaSetValues(E3dp_TranslateZW, XeNsensitive, True, NULL);
   E3dp_TransformObjectsSensitive=True;
  }
  if((E3dp_ScaleXW!=(Widget)NULL)&&(LScaleFlags&E3dDO_X)) XeScaleSetValueD(E3dp_ScaleXW, LScaleX, False);
  if((E3dp_ScaleYW!=(Widget)NULL)&&(LScaleFlags&E3dDO_Y)) XeScaleSetValueD(E3dp_ScaleYW, LScaleY, False);
  if((E3dp_ScaleZW!=(Widget)NULL)&&(LScaleFlags&E3dDO_Z)) XeScaleSetValueD(E3dp_ScaleZW, LScaleZ, False);
  if((E3dp_RotateXW!=(Widget)NULL)&&(LRotFlags&E3dDO_X)) XeScaleSetValueD(E3dp_RotateXW, LRotateX, False);
  if((E3dp_RotateYW!=(Widget)NULL)&&(LRotFlags&E3dDO_Y)) XeScaleSetValueD(E3dp_RotateYW, LRotateY, False);
  if((E3dp_RotateZW!=(Widget)NULL)&&(LRotFlags&E3dDO_Z)) XeScaleSetValueD(E3dp_RotateZW, LRotateZ, False);
  if((E3dp_TranslateXW!=(Widget)NULL)&&(LTransFlags&E3dDO_X)) XeScaleSetValueD(E3dp_TranslateXW, LTranslateX, False);
  if((E3dp_TranslateYW!=(Widget)NULL)&&(LTransFlags&E3dDO_Y)) XeScaleSetValueD(E3dp_TranslateYW, LTranslateY, False);
  if((E3dp_TranslateZW!=(Widget)NULL)&&(LTransFlags&E3dDO_Z)) XeScaleSetValueD(E3dp_TranslateZW, LTranslateZ, False);

  EFree(LModels);
 }
 else
 {
  if(LSelectionChanged)
  {
   LXeStr=XeStringVaCreate(True, XeSTRING_COMPONENT_CHARSET, "tag1",
	 XeSTRING_COMPONENT_ALIGN, XeSTRING_ALIGN_LEFT,
	 XeSTRING_COMPONENT_TEXT, "-\n\n\n\n",
	 XeSTRING_COMPONENT_UNKNOWN);

   if(LXeStr)
   {
    XtVaSetValues(_ModelNameLabelW, XeNlabelString, LXeStr, NULL);

    XeStringFree(LXeStr);
   }

   if(E3dp_ScaleXW) XtVaSetValues(E3dp_ScaleXW, XeNsensitive, False, NULL);
   if(E3dp_ScaleYW) XtVaSetValues(E3dp_ScaleYW, XeNsensitive, False, NULL);
   if(E3dp_ScaleZW) XtVaSetValues(E3dp_ScaleZW, XeNsensitive, False, NULL);
   if(E3dp_RotateXW) XtVaSetValues(E3dp_RotateXW, XeNsensitive, False, NULL);
   if(E3dp_RotateYW) XtVaSetValues(E3dp_RotateYW, XeNsensitive, False, NULL);
   if(E3dp_RotateZW) XtVaSetValues(E3dp_RotateZW, XeNsensitive, False, NULL);
   if(E3dp_TranslateXW) XtVaSetValues(E3dp_TranslateXW, XeNsensitive, False, NULL);
   if(E3dp_TranslateYW) XtVaSetValues(E3dp_TranslateYW, XeNsensitive, False, NULL);
   if(E3dp_TranslateZW) XtVaSetValues(E3dp_TranslateZW, XeNsensitive, False, NULL);
   E3dp_TransformObjectsSensitive=False;
  }
 }
}


//========================================================
// Refresh Scaling ScaleWidgets
//========================================================
void E3dp_RefreshScalingScales(E3dCoordinate LX, E3dCoordinate LY, E3dCoordinate LZ, int LFlags)
{
 if((E3dp_ScaleXW!=(Widget)NULL)&&(LFlags&E3dDO_X)) XeScaleSetValueD(E3dp_ScaleXW, LX, False);
 if((E3dp_ScaleYW!=(Widget)NULL)&&(LFlags&E3dDO_Y)) XeScaleSetValueD(E3dp_ScaleYW, LY, False);
 if((E3dp_ScaleZW!=(Widget)NULL)&&(LFlags&E3dDO_Z)) XeScaleSetValueD(E3dp_ScaleZW, LZ, False);
}

//========================================================
// Refresh Rotation ScaleWidgets
//========================================================
void E3dp_RefreshRotationScales(E3dCoordinate LX, E3dCoordinate LY, E3dCoordinate LZ, int LFlags)
{
 if((E3dp_RotateXW!=(Widget)NULL)&&(LFlags&E3dDO_X)) XeScaleSetValueD(E3dp_RotateXW, LX, False);
 if((E3dp_RotateYW!=(Widget)NULL)&&(LFlags&E3dDO_Y)) XeScaleSetValueD(E3dp_RotateYW, LY, False);
 if((E3dp_RotateZW!=(Widget)NULL)&&(LFlags&E3dDO_Z)) XeScaleSetValueD(E3dp_RotateZW, LZ, False);
}


//========================================================
// Refresh Translation ScaleWidgets
//========================================================
void E3dp_RefreshTranslationScales(E3dCoordinate LX, E3dCoordinate LY, E3dCoordinate LZ, int LFlags)
{
 if((E3dp_TranslateXW!=(Widget)NULL)&&(LFlags&E3dDO_X)) XeScaleSetValueD(E3dp_TranslateXW, LX, False);
 if((E3dp_TranslateYW!=(Widget)NULL)&&(LFlags&E3dDO_Y)) XeScaleSetValueD(E3dp_TranslateYW, LY, False);
 if((E3dp_TranslateZW!=(Widget)NULL)&&(LFlags&E3dDO_Z)) XeScaleSetValueD(E3dp_TranslateZW, LZ, False);
}



//================================================================
// Scaling, rotation and translation Scale Widget callback
//================================================================
void E3dpCB_TransformScaleW(Widget LW, XtPointer LClientData, XtPointer LCallData)
{
 E3dScene*			LScene=E3d_Scene;
 E3dModel**			LModels=NULL;
 E3dModel*			LRootModel;
 E3dModel*			LModel;
 E3dSceneModelsCallbackStruct	LCBS;
 E3dCoordinate			LFV;
 unsigned int			LC, LN, LNumOfModels=0;
 int				LReason=0;
 EBool				LChanged;


 LFV=((XeScaleCallbackStruct*)LCallData)->FloatValue;
 if(LFV==0.0) LFV=0.00001;


 for(LC=0, LN=E3d_Scene->NumOfRootModels;LC<LN;LC++)
 {
  LModel=LRootModel=LScene->RootModels[LC];
  LChanged=FALSE;

  for(;LModel;LModel=LModel->Next)
  {
   switch(LModel->Selection)
   {
    case E3dSEL_BRANCH_ROOT:
    case E3dSEL_NODE:
     switch((int)LClientData)
     {
      case E3dpSCALEX:
       if(LModel->Scaling.X!=LFV)
       {
	LModel->Scaling.X=LFV;
	LChanged=TRUE;

	ELst_AddPointer((void***)(&LModels), &LNumOfModels, LModel);
	LReason=E3dCR_NODE_SCALE;
       }
      break;

      case E3dpSCALEY:
       if(LModel->Scaling.Y!=LFV)
       {
	LModel->Scaling.Y=LFV;
	LChanged=TRUE;

	ELst_AddPointer((void***)(&LModels), &LNumOfModels, LModel);
	LReason=E3dCR_NODE_SCALE;
       }
      break;

      case E3dpSCALEZ:
       if(LModel->Scaling.Z!=LFV)
       {
	LModel->Scaling.Z=LFV;
	LChanged=TRUE;

	ELst_AddPointer((void***)(&LModels), &LNumOfModels, LModel);
	LReason=E3dCR_NODE_SCALE;
       }
      break;

      case E3dpROTATEX:
       if(LModel->Rotation.X!=LFV)
       {
	LModel->Rotation.X=LFV;
	LChanged=TRUE;

	ELst_AddPointer((void***)(&LModels), &LNumOfModels, LModel);
	LReason=E3dCR_NODE_ROTATE;
       }
      break;

      case E3dpROTATEY:
       if(LModel->Rotation.Y!=LFV)
       {
	LModel->Rotation.Y=LFV;
	LChanged=TRUE;

	ELst_AddPointer((void***)(&LModels), &LNumOfModels, LModel);
	LReason=E3dCR_NODE_ROTATE;
       }
      break;

      case E3dpROTATEZ:
       if(LModel->Rotation.Z!=LFV)
       {
	LModel->Rotation.Z=LFV;
	LChanged=TRUE;

	ELst_AddPointer((void***)(&LModels), &LNumOfModels, LModel);
	LReason=E3dCR_NODE_ROTATE;
       }
      break;

      case E3dpTRANSLATEX:
       if(LModel->Translation.X!=LFV)
       {
	LModel->Translation.X=LFV;
	LChanged=TRUE;

	ELst_AddPointer((void***)(&LModels), &LNumOfModels, LModel);
	LReason=E3dCR_NODE_TRANSLATE;
       }
      break;

      case E3dpTRANSLATEY:
       if(LModel->Translation.Y!=LFV)
       {
	LModel->Translation.Y=LFV;
	LChanged=TRUE;

	ELst_AddPointer((void***)(&LModels), &LNumOfModels, LModel);
	LReason=E3dCR_NODE_TRANSLATE;
       }
      break;

      case E3dpTRANSLATEZ:
       if(LModel->Translation.Z!=LFV)
       {
	LModel->Translation.Z=LFV;
	LChanged=TRUE;

	ELst_AddPointer((void***)(&LModels), &LNumOfModels, LModel);
	LReason=E3dCR_NODE_TRANSLATE;
       }
      break;
     }
    break;
   }
  }

  if(LChanged)
  {
   E3d_ModelHrcRefreshMatrices(LRootModel);

   if(E3d_GLDataTransformed) E3d_ModelHrcUpdateForDisplay(LRootModel, E3dGF_SHAPE);

   E3dp_Refresh3DWindows(E3dDF_ALL, E3dVM_ALL);
  }
 }

// Call Scene callbacks
//
 if(LModels)
 {
  LCBS.Reason=LReason;
  LCBS.Models=LModels;
  LCBS.NumOfModels=LNumOfModels;
  E3d_CallCallbacks(LScene, LScene->Callbacks, LScene->NumOfCallbacks, &LCBS);
  EFree(LModels);
 }
}


//================================================
// Set state of transformation ScaleWidgets
//================================================
void E3dp_SetTransScaleStates()
{
 if(E3dp_ScaleXBW) XeToggleButtonSetState(E3dp_ScaleXBW, (Boolean)((E3dp_ScaleFlags&E3dDO_X)!=0), False);
 if(E3dp_ScaleYBW) XeToggleButtonSetState(E3dp_ScaleYBW, (Boolean)((E3dp_ScaleFlags&E3dDO_Y)!=0), False);
 if(E3dp_ScaleZBW) XeToggleButtonSetState(E3dp_ScaleZBW, (Boolean)((E3dp_ScaleFlags&E3dDO_Z)!=0), False);

 if(E3dp_RotateXBW) XeToggleButtonSetState(E3dp_RotateXBW, (Boolean)((E3dp_RotateFlags&E3dDO_X)!=0), False);
 if(E3dp_RotateYBW) XeToggleButtonSetState(E3dp_RotateYBW, (Boolean)((E3dp_RotateFlags&E3dDO_Y)!=0), False);
 if(E3dp_RotateZBW) XeToggleButtonSetState(E3dp_RotateZBW, (Boolean)((E3dp_RotateFlags&E3dDO_Z)!=0), False);

 if(E3dp_TranslateXBW) XeToggleButtonSetState(E3dp_TranslateXBW, (Boolean)((E3dp_TranslateFlags&E3dDO_X)!=0), False);
 if(E3dp_TranslateYBW) XeToggleButtonSetState(E3dp_TranslateYBW, (Boolean)((E3dp_TranslateFlags&E3dDO_Y)!=0), False);
 if(E3dp_TranslateZBW) XeToggleButtonSetState(E3dp_TranslateZBW, (Boolean)((E3dp_TranslateFlags&E3dDO_Z)!=0), False);
}



//========================================================
// Enable/disable all components for a transformation
//========================================================
static void E3dpCB_TransformOnOff(Widget LW, XtPointer LClientData, XtPointer LCallData)
{
 E3dp_ResetWorkModes();
 switch((int)LClientData)
 {
  case E3dpSCALEX|E3dpSCALEY|E3dpSCALEZ:
   if(E3dp_ScaleFlags) E3dp_ScaleFlags=0;
   else { E3dp_ScaleFlags=E3dDO_X|E3dDO_Y|E3dDO_Z;E3dp_RotateFlags=E3dDO_NONE;E3dp_TranslateFlags=E3dDO_NONE; }
  break;

  case E3dpROTATEX|E3dpROTATEY|E3dpROTATEZ:
   if(E3dp_RotateFlags) E3dp_RotateFlags=0;
   else { E3dp_ScaleFlags=0;E3dp_RotateFlags=E3dDO_X|E3dDO_Y|E3dDO_Z;E3dp_TranslateFlags=0; }
  break;

  case E3dpTRANSLATEX|E3dpTRANSLATEY|E3dpTRANSLATEZ:
   if(E3dp_TranslateFlags) E3dp_TranslateFlags=0;
   else { E3dp_ScaleFlags=0;E3dp_RotateFlags=0;E3dp_TranslateFlags=E3dDO_X|E3dDO_Y|E3dDO_Z; }
  break;
 }
 E3dp_SetTransScaleStates();
}


//================================================================
// Enable/disable a single component for a transformation
//================================================================
static void E3dpCB_TransformComponentOnOff(Widget LW, XtPointer LClientData, XtPointer LCallData)
{
 XEvent	*	LXEv=((XeAnyCallbackStruct*)LCallData)->event;
 Boolean	LValue=((XeToggleButtonCallbackStruct*)LCallData)->set;

 switch(LXEv->type)
 {
  case ButtonPress:
   E3dp_ResetWorkModes();
   switch(LXEv->xbutton.button)
   {
    case Button1:
     switch((int)LClientData)
     {
      case E3dpSCALEX|E3dpSCALEY|E3dpSCALEZ:
       if(E3dp_ScaleFlags) E3dp_ScaleFlags=0;
       else E3dp_ScaleFlags=E3dDO_X|E3dDO_Y|E3dDO_Z;
      break;

      case E3dpSCALEX:
       if(LValue) { E3dp_ScaleFlags|=E3dDO_X;E3dp_RotateFlags=E3dDO_NONE;E3dp_TranslateFlags=E3dDO_NONE; }
       else E3dp_ScaleFlags&=(E3dDO_ALL-E3dDO_X);
      break;

      case E3dpSCALEY:
       if(LValue) { E3dp_ScaleFlags|=E3dDO_Y;E3dp_RotateFlags=E3dDO_NONE;E3dp_TranslateFlags=E3dDO_NONE; }
       else E3dp_ScaleFlags&=(E3dDO_ALL-E3dDO_Y);
      break;

      case E3dpSCALEZ:
       if(LValue) { E3dp_ScaleFlags|=E3dDO_Z;E3dp_RotateFlags=E3dDO_NONE;E3dp_TranslateFlags=E3dDO_NONE; }
       else E3dp_ScaleFlags&=(E3dDO_ALL-E3dDO_Z);
      break;

      case E3dpROTATEX:
       if(LValue) { E3dp_ScaleFlags=0;E3dp_RotateFlags|=E3dDO_X;E3dp_TranslateFlags=E3dDO_NONE; }
       else E3dp_RotateFlags&=(E3dDO_ALL-E3dDO_X);
      break;

      case E3dpROTATEY:
       if(LValue) { E3dp_ScaleFlags=0;E3dp_RotateFlags|=E3dDO_Y;E3dp_TranslateFlags=E3dDO_NONE; }
       else E3dp_RotateFlags&=(E3dDO_ALL-E3dDO_Y);
      break;

      case E3dpROTATEZ:
       if(LValue) { E3dp_ScaleFlags=0;E3dp_RotateFlags|=E3dDO_Z;E3dp_TranslateFlags=E3dDO_NONE; }
       else E3dp_RotateFlags&=(E3dDO_ALL-E3dDO_Z);
      break;

      case E3dpTRANSLATEX:
       if(LValue) { E3dp_ScaleFlags=0;E3dp_RotateFlags=0;E3dp_TranslateFlags|=E3dDO_X; }
       else E3dp_TranslateFlags&=(E3dDO_ALL-E3dDO_X);
      break;

      case E3dpTRANSLATEY:
       if(LValue) { E3dp_ScaleFlags=0;E3dp_RotateFlags=0;E3dp_TranslateFlags|=E3dDO_Y; }
       else E3dp_TranslateFlags&=(E3dDO_ALL-E3dDO_Y);
      break;

      case E3dpTRANSLATEZ:
       if(LValue) { E3dp_ScaleFlags=0;E3dp_RotateFlags=0;E3dp_TranslateFlags|=E3dDO_Z; }
       else E3dp_TranslateFlags&=(E3dDO_ALL-E3dDO_Z);
      break;
     }
    break;

    case Button2:
     switch((int)LClientData)
     {
      case E3dpSCALEX:
      case E3dpSCALEY:
      case E3dpSCALEZ:
       if(E3dp_ScaleFlags!=(E3dDO_X|E3dDO_Y|E3dDO_Z)) { E3dp_ScaleFlags=E3dDO_X|E3dDO_Y|E3dDO_Z;E3dp_RotateFlags=E3dDO_NONE;E3dp_TranslateFlags=E3dDO_NONE; }
       else E3dp_ScaleFlags=E3dDO_NONE;
      break;

      case E3dpROTATEX:
      case E3dpROTATEY:
      case E3dpROTATEZ:
       if(E3dp_RotateFlags!=(E3dDO_X|E3dDO_Y|E3dDO_Z)) { E3dp_RotateFlags=E3dDO_X|E3dDO_Y|E3dDO_Z;E3dp_ScaleFlags=E3dDO_NONE;E3dp_TranslateFlags=E3dDO_NONE; }
       else E3dp_RotateFlags=E3dDO_NONE;
      break;

      case E3dpTRANSLATEX:
      case E3dpTRANSLATEY:
      case E3dpTRANSLATEZ:
       if(E3dp_TranslateFlags!=(E3dDO_X|E3dDO_Y|E3dDO_Z)) { E3dp_TranslateFlags=E3dDO_X|E3dDO_Y|E3dDO_Z;E3dp_ScaleFlags=E3dDO_NONE;E3dp_RotateFlags=E3dDO_NONE; }
       else E3dp_TranslateFlags=E3dDO_NONE;
      break;
     }
    break;
   }
  break;
 }
 E3dp_SetTransScaleStates();
}


static Widget _CreateTransformComponentWidgets(Widget LParentW, char* LName, Widget* LScaleWRet, XtPointer LClientD, double LResetValue, char* LToolTip)
{
 Widget		LW;
 XeString	LXeStr;


 EXtStart;
 EXtSetArg(XeNbackground, MenuBackgroundColor);
 EXtSetArg(XeNmarginHeight, 0);EXtSetArg(XeNmarginLeft, 3);EXtSetArg(XeNmarginTop, 0);EXtSetArg(XeNmarginBottom, 0);
 EXtSetArg(XeNlabelColor, WhitePixel(E3dp_Display, E3dp_Screen));EXtSetArg(XeNlabelFontList, EGUI_LabelFontList);
 EXtSetArg(XeNtextBackground, EGUI_TextFieldBackgroundColor);
 EXtSetArg(XeNtextForeground, EGUI_TextFieldForegroundColor);
 EXtSetArg(XeNcursorColor, WhitePixel(EGUI_Display, EGUI_ScreenNumber));
 EXtSetArg(XeNtextFieldFontList, EGUI_NumFontList);
 EXtSetArg(XeNminimum, -1000000000);EXtSetArg(XeNmaximum, 1000000000);EXtSetArg(XeNdecimalPoints, 4);
 EXtSetArg(XeNvalue, LResetValue*10000);EXtSetArg(XeNresetValue, LResetValue*10000);
 EXtSetArg(XeNorientation, XeHORIZONTAL);EXtSetArg(XeNshadowType, XeSHADOW_IN);
 EXtSetArg(XeNlabelShadow, 2);
 EXtSetArg(XeNactivateOnLeave, True);
 EXtSetArg(XeNtextFieldDigits, 7);
 EXtSetArg(XeNcreateScale, False);
 EXtSetArg(XeNcreateResetButton, False);
 EXtSetArg(XeNwidthAdjustable, True);
 EXtSetArg(XeNtextFieldShadowThickness, 1);
 *LScaleWRet=XtCreateManagedWidget(LName, xeScaleWidgetClass, LParentW, Args, ArgCnt);
 XtAddCallback(*LScaleWRet, XeNvalueChangedCallback, E3dpCB_TransformScaleW, (XtPointer)LClientD);


 EXtStart;
 EXtSetArg(XeNbackground, MenuBackgroundColor);
 switch(LName[0])
 {
  case 'X':	EXtSetArg(XeNselectColor, E3dp_RedPixel);break;
  case 'Y':	EXtSetArg(XeNselectColor, E3dp_GreenPixel);break;
  case 'Z':	EXtSetArg(XeNselectColor, E3dp_BluePixel);break;
 }
 EXtSetArg(XeNfontList, EGUI_NumFontList);
 EXtSetArg(XeNmarginHeight, 1);EXtSetArg(XeNmarginWidth, 0);EXtSetArg(XeNmarginLeft, 0);
 EXtSetArg(XeNshadowThickness, 0);EXtSetArg(XeNgapWidth, 1);
// EXtSetArg(XeNgapColor, MenuBackgroundColor);
 EXtSetArg(XeNactivateOnArm, True);
 EXtSetArg(XeNindicatorType, XeN_OF_MANY_FULL_HEIGHT);
 EXtSetArg(XeNindicatorXSize, 12);	//EXtSetArg(XeNindicatorGapWidth, 0);
 if((LXeStr=XeStringVaCreate(True, XeSTRING_COMPONENT_CHARSET, "tag1", XeSTRING_COMPONENT_ALIGN, XeSTRING_ALIGN_LEFT, XeSTRING_COMPONENT_TEXT, LToolTip, XeSTRING_COMPONENT_UNKNOWN))!=NULL)
 {
  EXtSetArg(XeNtipBackground, Ec_XGetRGBAiColor(E3dp_Display, DefaultColormap(E3dp_Display, E3dp_Screen), &EGUI_ToolTipBackgroundRGBAiColor));
  EXtSetArg(XeNtipFontList, EGUI_LabelFontList);
  EXtSetArg(XeNtipString, LXeStr);
 }
 EXtSetArg(XeNwidthAdjustable, False);
 LW=XtCreateManagedWidget(NULL, xeToggleButtonWidgetClass, LParentW, Args, ArgCnt);
 if(LXeStr) XeStringFree(LXeStr);
 XtAddCallback(LW, XeNvalueChangedCallback, E3dpCB_TransformComponentOnOff, (XtPointer)LClientD);


 return(LW);
}


//========================================
// Scene Model add/remove callback
//========================================
static void _CB_SceneAddRemove(void* LScn, EPointer LClientData, EPointer LCallData)
{
 E3dp_TransformPanelUpdate(E3dDO_ALL, E3dDO_ALL, E3dDO_ALL, TRUE);
}


//========================================
// Turn transformation panel on/off
//========================================
void E3dp_TransformPanelToggle(E3dPanel* L3DPanel, EBool LSwitch)
{

 if(E3dp_TransformPanelW==NULL)
 {
  Widget	LPanelW, LTAW, LPW;
  EguiVisual*	LVisual=EGUI_TruecolorVisual;


  if(LSwitch)
  {
   XeString		LXeStr, LXeStr1;
   EWidthAlignRec	LWAs[6];
   int			LXSize, LC, LWAN;


   EXtStart;
   EXtSetArg(XeNbackground, MenuBackgroundColor);
   EXtSetArg(XeNorientation, XeVERTICAL);
   EXtSetArg(XeNmarginWidth, 0);EXtSetArg(XeNmarginHeight, 0);
   EXtSetArg(XeNadjustEntryWidths, True);
   EXtSetArg(XeNwidthAdjustable, False);
   EXtSetArg(XeNheightAdjustable, True);
   E3dp_TransformPanelW=XtCreateWidget("TopTransformMatrix", xeMatrixWidgetClass, L3DPanel->MiddleMatrixW, Args, ArgCnt);


   EXtStart;
   EXtStart;
   EXtSetArg(XeNleftAttachment, XeATTACH_FORM);EXtSetArg(XeNleftOffset, 0);
   EXtSetArg(XeNrightAttachment, XeATTACH_FORM);EXtSetArg(XeNrightOffset, 0);
   EXtSetArg(XeNtopAttachment, XeATTACH_FORM);EXtSetArg(XeNtopOffset, 0);
  /*
   if(LVisual)
   {
    EXtSetArg(XeNvisualInfo, LVisual->X_VisualInfo);
    EXtSetArg(XeNcolormap, LVisual->X_Colormap);
   }
  */
   EXtSetArg(XeNgapWidth, 0);EXtSetArg(XeNshadowThickness, 2);EXtSetArg(XeNmarginHeight, 2);
   EXtSetArg(XeNbackground, EGUI_ButtonColor);
   EXtSetArg(XeNhighlightThickness, 0);
   EXtSetArg(XeNfontList, EGUI_LabelFontList);
   EXtSetArg(XeNheightAdjustable, False);
   LTAW=XtCreateManagedWidget("Transform", xeLabelWidgetClass, E3dp_TransformPanelW, Args, ArgCnt);


   EXtStart;
   EXtSetArg(XeNshadowThickness, 2);EXtSetArg(XeNshadowType, XeSHADOW_OUT);
   EXtSetArg(XeNbackground, MenuBackgroundColor);
   EXtSetArg(XeNorientation, XeVERTICAL);
   EXtSetArg(XeNySpacing, 5);
   EXtSetArg(XeNmarginWidth, 4);EXtSetArg(XeNmarginHeight, 4);
   EXtSetArg(XeNadjustEntryWidths, True);
   EXtSetArg(XeNwidthAdjustable, False);
   EXtSetArg(XeNheightAdjustable, True);
   LPanelW=XtCreateManagedWidget("TransformMatrix", xeMatrixWidgetClass, E3dp_TransformPanelW, Args, ArgCnt);


// Scaling
//
   EXtStart;
   EXtSetArg(XeNmarginTop, 4);
   EXtSetArg(XeNshadowThickness, 0);
   EXtSetArg(XeNlabelShadow, 2);
   EXtSetArg(XeNfontList, EGUI_LabelFontList);
   EXtSetArg(XeNactivateOnArm, True);
   if(LVisual)
   {
    EXtSetArg(XeNvisualInfo, LVisual->X_VisualInfo);
    EXtSetArg(XeNcolormap, LVisual->X_Colormap);
    EXtSetArg(XeNbackground, Ec_XGetRGBAiColor(EGUI_Display, LVisual->X_Colormap, &(E3dp_Prefs.MenuRGBAiColor)));
    EXtSetArg(XeNforeground, Ec_XGetRGBAiColor(EGUI_Display, LVisual->X_Colormap, &(EGUI_WhiteRGBAiColor)));
    E3dM_SetIconL(Image_XKey);
   }
   else
   {
    EXtSetArg(XeNbackground, MenuBackgroundColor);
    EXtSetArg(XeNforeground, WhitePixel(E3dp_Display, E3dp_Screen));
   }
   _E3dM_SetToolTip("Enable/disable interactive scaling along X, Y and Z");
   EXtSetArg(XeNheightAdjustable, False);
   EXtSetArg(XeNlabelXAlignment, XeALIGN_BEGINNING);
   LTAW=XtCreateManagedWidget("Scaling", xePushButtonWidgetClass, LPanelW, Args, ArgCnt);
   if(LXeStr) XeStringFree(LXeStr);
   XtAddCallback(LTAW, XeNactivateCallback, E3dpCB_TransformOnOff, (XtPointer)(E3dpSCALEX|E3dpSCALEY|E3dpSCALEZ));



   EXtStart;
   EXtSetArg(XeNtopAttachment, XeATTACH_WIDGET);EXtSetArg(XeNtopWidget, LTAW);EXtSetArg(XeNtopOffset, 4);
   EXtSetArg(XeNleftAttachment, XeATTACH_FORM);EXtSetArg(XeNleftOffset, 7);
   EXtSetArg(XeNspacing, 0);
   EXtSetArg(XeNmarginWidth, 0);EXtSetArg(XeNmarginHeight, 0);
   EXtSetArg(XeNorientation, XeHORIZONTAL);
   EXtSetArg(XeNbackground, MenuBackgroundColor);
   EXtSetArg(XeNshadowType, XeSHADOW_IN);
   EXtSetArg(XeNtopShadowColor, MenuTopShadowColor);
   EXtSetArg(XeNbottomShadowColor, MenuBottomShadowColor);
   EXtSetArg(XeNorientation, XeHORIZONTAL);
   EXtSetArg(XeNnumOfColumns, 2);
   EXtSetArg(XeNadjustEntryWidths, True);
   EXtSetArg(XeNheightAdjustable, False);
   LTAW=XtCreateManagedWidget("ScaleMatrixW", xeMatrixWidgetClass, LPanelW, Args, ArgCnt);

   E3dp_ScaleXBW=_CreateTransformComponentWidgets(LTAW, "X:", &E3dp_ScaleXW, (XtPointer)E3dpSCALEX, 1.0, "Enable/disable interactive scaling along the X axis");
   E3dp_ScaleYBW=_CreateTransformComponentWidgets(LTAW, "Y:", &E3dp_ScaleYW, (XtPointer)E3dpSCALEY, 1.0, "Enable/disable interactive scaling along the Y axis");
   E3dp_ScaleZBW=_CreateTransformComponentWidgets(LTAW, "Z:", &E3dp_ScaleZW, (XtPointer)E3dpSCALEZ, 1.0, "Enable/disable interactive scaling along the Z axis");

// Indent Scale widgets
//
   LWAN=0;
   LWAs[LWAN++].W=E3dp_ScaleXW;
   LWAs[LWAN++].W=E3dp_ScaleYW;
   LWAs[LWAN++].W=E3dp_ScaleZW;
   for(LC=0;LC<LWAN;LC++) XtVaGetValues(LWAs[LC].W, XeNlabelXSize, &(LWAs[LC].XSize), NULL);
   for(LC=1, LXSize=LWAs[0].XSize;LC<LWAN;LC++) { if(LWAs[LC].XSize>LXSize) LXSize=LWAs[LC].XSize; }
   for(LC=0;LC<LWAN;LC++) { XtVaSetValues(LWAs[LC].W, XeNmarginLeft, LXSize-LWAs[LC].XSize+2, NULL); }



// Rotation
//
   EXtStart;
   EXtSetArg(XeNleftAttachment, XeATTACH_FORM);EXtSetArg(XeNleftOffset, 7);
   EXtSetArg(XeNtopAttachment, XeATTACH_WIDGET);EXtSetArg(XeNtopWidget, LTAW);EXtSetArg(XeNtopOffset, 12);
   EXtSetArg(XeNmarginTop, 4);
   EXtSetArg(XeNshadowThickness, 0);
   EXtSetArg(XeNlabelShadow, 2);
   EXtSetArg(XeNfontList, EGUI_LabelFontList);
   EXtSetArg(XeNactivateOnArm, True);
   if(LVisual)
   {
    EXtSetArg(XeNvisualInfo, LVisual->X_VisualInfo);
    EXtSetArg(XeNcolormap, LVisual->X_Colormap);
    EXtSetArg(XeNbackground, Ec_XGetRGBAiColor(EGUI_Display, LVisual->X_Colormap, &(E3dp_Prefs.MenuRGBAiColor)));
    EXtSetArg(XeNforeground, Ec_XGetRGBAiColor(EGUI_Display, LVisual->X_Colormap, &(EGUI_WhiteRGBAiColor)));
    E3dM_SetIconL(Image_CKey);
   }
   else
   {
    EXtSetArg(XeNbackground, MenuBackgroundColor);
    EXtSetArg(XeNforeground, WhitePixel(E3dp_Display, E3dp_Screen));
   }
   _E3dM_SetToolTip("Enable/disable interactive rotating about X, Y and Z");
   EXtSetArg(XeNlabelXAlignment, XeALIGN_BEGINNING);
   EXtSetArg(XeNheightAdjustable, False);
   LTAW=XtCreateManagedWidget("Rotation", xePushButtonWidgetClass, LPanelW, Args, ArgCnt);
   if(LXeStr) XeStringFree(LXeStr);
   XtAddCallback(LTAW, XeNactivateCallback, E3dpCB_TransformOnOff, (XtPointer)(E3dpROTATEX|E3dpROTATEY|E3dpROTATEZ));

   EXtStart;
   EXtSetArg(XeNtopAttachment, XeATTACH_WIDGET);EXtSetArg(XeNtopWidget, LTAW);EXtSetArg(XeNtopOffset, 4);
   EXtSetArg(XeNleftAttachment, XeATTACH_FORM);EXtSetArg(XeNleftOffset, 7);
   EXtSetArg(XeNspacing, 0);
   EXtSetArg(XeNmarginWidth, 0);EXtSetArg(XeNmarginHeight, 0);
   EXtSetArg(XeNorientation, XeHORIZONTAL);
   EXtSetArg(XeNbackground, MenuBackgroundColor);
//   EXtSetArg(XeNshadowThickness, 1);
   EXtSetArg(XeNshadowType, XeSHADOW_IN);
   EXtSetArg(XeNtopShadowColor, MenuTopShadowColor);
   EXtSetArg(XeNbottomShadowColor, MenuBottomShadowColor);
   EXtSetArg(XeNorientation, XeHORIZONTAL);
   EXtSetArg(XeNnumOfColumns, 2);
   EXtSetArg(XeNadjustEntryWidths, True);
   EXtSetArg(XeNheightAdjustable, False);
   LTAW=XtCreateManagedWidget("RotateMatrixW", xeMatrixWidgetClass, LPanelW, Args, ArgCnt);

   E3dp_RotateXBW=_CreateTransformComponentWidgets(LTAW, "X:", &E3dp_RotateXW, (XtPointer)E3dpROTATEX, 0.0, "Enable/disable interactive rotation about the X axis");
   E3dp_RotateYBW=_CreateTransformComponentWidgets(LTAW, "Y:", &E3dp_RotateYW, (XtPointer)E3dpROTATEY, 0.0, "Enable/disable interactive rotation about the Y axis");
   E3dp_RotateZBW=_CreateTransformComponentWidgets(LTAW, "Z:", &E3dp_RotateZW, (XtPointer)E3dpROTATEZ, 0.0, "Enable/disable interactive rotation about the Z axis");

// Indent Scale widgets
//
   LWAN=0;
   LWAs[LWAN++].W=E3dp_RotateXW;
   LWAs[LWAN++].W=E3dp_RotateYW;
   LWAs[LWAN++].W=E3dp_RotateZW;
   for(LC=0;LC<LWAN;LC++) XtVaGetValues(LWAs[LC].W, XeNlabelXSize, &(LWAs[LC].XSize), NULL);
   for(LC=1, LXSize=LWAs[0].XSize;LC<LWAN;LC++) { if(LWAs[LC].XSize>LXSize) LXSize=LWAs[LC].XSize; }
   for(LC=0;LC<LWAN;LC++) { XtVaSetValues(LWAs[LC].W, XeNmarginLeft, LXSize-LWAs[LC].XSize+2, NULL); }

// Translation
//
   EXtStart;
   EXtSetArg(XeNleftAttachment, XeATTACH_FORM);EXtSetArg(XeNleftOffset, 7);
   EXtSetArg(XeNtopAttachment, XeATTACH_WIDGET);EXtSetArg(XeNtopWidget, LTAW);EXtSetArg(XeNtopOffset, 12);
   EXtSetArg(XeNmarginTop, 4);
   EXtSetArg(XeNshadowThickness, 0);
   EXtSetArg(XeNlabelShadow, 2);
   EXtSetArg(XeNfontList, EGUI_LabelFontList);
   EXtSetArg(XeNactivateOnArm, True);
   if(LVisual)
   {
    EXtSetArg(XeNvisualInfo, LVisual->X_VisualInfo);
    EXtSetArg(XeNcolormap, LVisual->X_Colormap);
    EXtSetArg(XeNbackground, Ec_XGetRGBAiColor(EGUI_Display, LVisual->X_Colormap, &(E3dp_Prefs.MenuRGBAiColor)));
    EXtSetArg(XeNforeground, Ec_XGetRGBAiColor(EGUI_Display, LVisual->X_Colormap, &(EGUI_WhiteRGBAiColor)));
    E3dM_SetIconL(Image_VKey);
   }
   else
   {
    EXtSetArg(XeNbackground, MenuBackgroundColor);
    EXtSetArg(XeNforeground, WhitePixel(E3dp_Display, E3dp_Screen));
   }
   _E3dM_SetToolTip("Enable/disable interactive translating along X, Y and Z");
   EXtSetArg(XeNlabelXAlignment, XeALIGN_BEGINNING);
   EXtSetArg(XeNheightAdjustable, False);
   LTAW=XtCreateManagedWidget("Translation", xePushButtonWidgetClass, LPanelW, Args, ArgCnt);
   if(LXeStr) XeStringFree(LXeStr);
   XtAddCallback(LTAW, XeNactivateCallback, E3dpCB_TransformOnOff, (XtPointer)(E3dpTRANSLATEX|E3dpTRANSLATEY|E3dpTRANSLATEZ));

   EXtStart;
   EXtSetArg(XeNtopAttachment, XeATTACH_WIDGET);EXtSetArg(XeNtopWidget, LTAW);EXtSetArg(XeNtopOffset, 4);
   EXtSetArg(XeNleftAttachment, XeATTACH_FORM);EXtSetArg(XeNleftOffset, 7);
   EXtSetArg(XeNspacing, 0);
   EXtSetArg(XeNmarginWidth, 0);EXtSetArg(XeNmarginHeight, 0);
   EXtSetArg(XeNorientation, XeHORIZONTAL);
   EXtSetArg(XeNbackground, MenuBackgroundColor);
   EXtSetArg(XeNshadowType, XeSHADOW_IN);
   EXtSetArg(XeNtopShadowColor, MenuTopShadowColor);
   EXtSetArg(XeNbottomShadowColor, MenuBottomShadowColor);
   EXtSetArg(XeNorientation, XeHORIZONTAL);
   EXtSetArg(XeNnumOfColumns, 2);
   EXtSetArg(XeNadjustEntryWidths, True);
   EXtSetArg(XeNheightAdjustable, False);
   LTAW=XtCreateManagedWidget("TranslateMatrixW", xeMatrixWidgetClass, LPanelW, Args, ArgCnt);

   E3dp_TranslateXBW=_CreateTransformComponentWidgets(LTAW, "X:", &E3dp_TranslateXW, (XtPointer)E3dpTRANSLATEX, 0.0, "Enable/disable interactive translating along the X axis");
   E3dp_TranslateYBW=_CreateTransformComponentWidgets(LTAW, "Y:", &E3dp_TranslateYW, (XtPointer)E3dpTRANSLATEY, 0.0, "Enable/disable interactive translating along the Y axis");
   E3dp_TranslateZBW=_CreateTransformComponentWidgets(LTAW, "Z:", &E3dp_TranslateZW, (XtPointer)E3dpTRANSLATEZ, 0.0, "Enable/disable interactive translating along the Z axis");

// Indent Scale widgets
//
   LWAN=0;
   LWAs[LWAN++].W=E3dp_TranslateXW;
   LWAs[LWAN++].W=E3dp_TranslateYW;
   LWAs[LWAN++].W=E3dp_TranslateZW;
   for(LC=0;LC<LWAN;LC++) XtVaGetValues(LWAs[LC].W, XeNlabelXSize, &(LWAs[LC].XSize), NULL);
   for(LC=1, LXSize=LWAs[0].XSize;LC<LWAN;LC++) { if(LWAs[LC].XSize>LXSize) LXSize=LWAs[LC].XSize; }
   for(LC=0;LC<LWAN;LC++) { XtVaSetValues(LWAs[LC].W, XeNmarginLeft, LXSize-LWAs[LC].XSize+2, NULL); }


   XtVaSetValues(E3dp_ScaleXW, XeNtraverseUp, E3dp_TranslateZW, XeNtraverseDown, E3dp_ScaleYW, NULL);
   XtVaSetValues(E3dp_ScaleYW, XeNtraverseUp, E3dp_ScaleXW, XeNtraverseDown, E3dp_ScaleZW, NULL);
   XtVaSetValues(E3dp_ScaleZW, XeNtraverseUp, E3dp_ScaleYW, XeNtraverseDown, E3dp_RotateXW, NULL);
   XtVaSetValues(E3dp_RotateXW, XeNtraverseUp, E3dp_ScaleZW, XeNtraverseDown, E3dp_RotateYW, NULL);
   XtVaSetValues(E3dp_RotateYW, XeNtraverseUp, E3dp_RotateXW, XeNtraverseDown, E3dp_RotateZW, NULL);
   XtVaSetValues(E3dp_RotateZW, XeNtraverseUp, E3dp_RotateYW, XeNtraverseDown, E3dp_TranslateXW, NULL);
   XtVaSetValues(E3dp_TranslateXW, XeNtraverseUp, E3dp_RotateZW, XeNtraverseDown, E3dp_TranslateYW, NULL);
   XtVaSetValues(E3dp_TranslateYW, XeNtraverseUp, E3dp_TranslateXW, XeNtraverseDown, E3dp_TranslateZW, NULL);
   XtVaSetValues(E3dp_TranslateZW, XeNtraverseUp, E3dp_TranslateYW, XeNtraverseDown, E3dp_ScaleXW, NULL);


   EXtStart;
   EXtSetArg(XeNshadowThickness, 0);
   EXtSetArg(XeNbackground, MenuBackgroundColor);
   EXtSetArg(XeNforeground, WhitePixel(E3dp_Display, E3dp_Screen));
   EXtSetArg(XeNleftAttachment, XeATTACH_FORM);EXtSetArg(XeNleftOffset, 4);
   EXtSetArg(XeNrightAttachment, XeATTACH_FORM);EXtSetArg(XeNrightOffset, 4);
   EXtSetArg(XeNtopAttachment, XeATTACH_WIDGET);EXtSetArg(XeNtopWidget, LTAW);EXtSetArg(XeNtopOffset, 10);
   EXtSetArg(XeNlabelShadow, 2);
   EXtSetArg(XeNfontList, EGUI_LabelFontList);
   EXtSetArg(XeNheightAdjustable, False);
   LTAW=XtCreateManagedWidget("What", xeLabelWidgetClass, LPanelW, Args, ArgCnt);

   EXtStart;
   EXtSetArg(XeNtopAttachment, XeATTACH_WIDGET);EXtSetArg(XeNtopWidget, LTAW);EXtSetArg(XeNtopOffset, 2);
   EXtSetArg(XeNleftAttachment, XeATTACH_FORM);EXtSetArg(XeNleftOffset, 7);
   EXtSetArg(XeNspacing, 0);
   EXtSetArg(XeNmarginWidth, 0);EXtSetArg(XeNmarginHeight, 0);
   EXtSetArg(XeNorientation, XeHORIZONTAL);
   EXtSetArg(XeNbackground, MenuBackgroundColor);
   EXtSetArg(XeNshadowThickness, 1);
   EXtSetArg(XeNshadowType, XeSHADOW_IN);
   EXtSetArg(XeNtopShadowColor, MenuTopShadowColor);
   EXtSetArg(XeNbottomShadowColor, MenuBottomShadowColor);
   EXtSetArg(XeNorientation, XeHORIZONTAL);
   EXtSetArg(XeNnumOfColumns, 2);
   EXtSetArg(XeNradioBehavior, True);
   EXtSetArg(XeNradioAlwaysOne, True);
   EXtSetArg(XeNheightAdjustable, False);
   E3dp_TransformTargetW=LPW=LTAW=XtCreateManagedWidget("TransTargetMatrixW", xeMatrixWidgetClass, LPanelW, Args, ArgCnt);

   _TTB_ObjectW=_CreateTransTargetButton("Obj", LPW, E3dpTT_OBJECT, "Transform selected objects (Models)");
   _TTB_TagW=_CreateTransTargetButton("Tag", LPW, E3dpTT_TAG, "Transform tagged points");
//   _CreateTransTargetButton("Ctr", LPW, E3dpTT_CENTER, "Transform center of selected objects");
//   _CreateTransTargetButton("Txt", LPW, E3dpTT_TEXTURE, "Transform texture projection");



   EXtStart;
   EXtSetArg(XeNshadowThickness, 0);
   EXtSetArg(XeNbackground, MenuBackgroundColor);
   EXtSetArg(XeNforeground, WhitePixel(E3dp_Display, E3dp_Screen));
   EXtSetArg(XeNleftAttachment, XeATTACH_FORM);EXtSetArg(XeNleftOffset, 7);
   EXtSetArg(XeNrightAttachment, XeATTACH_FORM);EXtSetArg(XeNrightOffset, 4);
   EXtSetArg(XeNtopAttachment, XeATTACH_WIDGET);EXtSetArg(XeNtopWidget, LTAW);EXtSetArg(XeNtopOffset, 3);
   EXtSetArg(XeNlabelShadow, 2);
   EXtSetArg(XeNfontList, EGUI_LabelFontList);
   EXtSetArg(XeNheightAdjustable, False);
   LTAW=XtCreateManagedWidget("How", xeLabelWidgetClass, LPanelW, Args, ArgCnt);

   EXtStart;
   EXtSetArg(XeNtopAttachment, XeATTACH_WIDGET);EXtSetArg(XeNtopWidget, LTAW);EXtSetArg(XeNtopOffset, 2);
   EXtSetArg(XeNbottomAttachment, XeATTACH_FORM);EXtSetArg(XeNbottomOffset, 7);
   EXtSetArg(XeNleftAttachment, XeATTACH_FORM);EXtSetArg(XeNleftOffset, 7);
   EXtSetArg(XeNspacing, 0);
   EXtSetArg(XeNmarginWidth, 0);EXtSetArg(XeNmarginHeight, 0);
   EXtSetArg(XeNorientation, XeHORIZONTAL);
   EXtSetArg(XeNbackground, MenuBackgroundColor);
   EXtSetArg(XeNshadowThickness, 1);
   EXtSetArg(XeNshadowType, XeSHADOW_IN);
   EXtSetArg(XeNtopShadowColor, MenuTopShadowColor);
   EXtSetArg(XeNbottomShadowColor, MenuBottomShadowColor);
   EXtSetArg(XeNorientation, XeHORIZONTAL);
   EXtSetArg(XeNnumOfColumns, 2);
   EXtSetArg(XeNradioBehavior, True);
   EXtSetArg(XeNradioAlwaysOne, True);
   EXtSetArg(XeNheightAdjustable, False);
   E3dp_TransformModeW=LPW=XtCreateManagedWidget("TransModeMatrixW", xeMatrixWidgetClass, LPanelW, Args, ArgCnt);

   _CreateTransModeButton("View", LPW, E3dpTM_VIEW, "Transform along the view-plane with the mouse");
   _CreateTransModeButton("Lcl", LPW, E3dpTM_LOCAL, "Transform in the selected object's local coordinate system with the mouse");
//   _CreateTransModeButton("Plane", LPW, E3dpTM_PLANE, "Transform along a given plane with the mouse");
//   _CreateTransModeButton("Glbl", LPW, E3dpTM_GLOBAL, "Transform in the world coordinate system with the mouse");


   EXtStart;
   EXtSetArg(XeNshadowThickness, 1);EXtSetArg(XeNshadowType, XeSHADOW_IN);
   EXtSetArg(XeNbackground, EGUI_ButtonColor);
   EXtSetArg(XeNforeground, EGUI_TopShadowColor);
   EXtSetArg(XeNlabelXAlignment, XeALIGN_BEGINNING);
   EXtSetArg(XeNlabelYAlignment, XeALIGN_BEGINNING);
   EXtSetArg(XeNmarginWidth, 4);EXtSetArg(XeNmarginHeight, 4);
   EXtSetArg(XeNhighlightThickness, 0);
   EXtSetArg(XeNfontList, EGUI_LabelFontList);
   EXtSetArg(XeNrecomputeSize, False);
   if((LXeStr1=XeStringVaCreate(True, XeSTRING_COMPONENT_CHARSET, "tag1", XeSTRING_COMPONENT_ALIGN, XeSTRING_ALIGN_LEFT, XeSTRING_COMPONENT_TEXT,
	 "List of selected Models. The transformation will apply to these.\nIf nothing is selected, this field just shows \"-\"\nIf there are more than 5 Models selected,\nthere will be a \"-->\" after the name of the last one.", XeSTRING_COMPONENT_UNKNOWN))!=NULL)
   {
    EXtSetArg(XeNtipBackground, Ec_XGetRGBAiColor(E3dp_Display, DefaultColormap(E3dp_Display, E3dp_Screen), &EGUI_ToolTipBackgroundRGBAiColor));
    EXtSetArg(XeNtipFontList, EGUI_LabelFontList);
    EXtSetArg(XeNtipString, LXeStr1);
   }
   LXeStr=XeStringVaCreate(True, XeSTRING_COMPONENT_CHARSET, "tag1",
	XeSTRING_COMPONENT_ALIGN, XeSTRING_ALIGN_LEFT,
	XeSTRING_COMPONENT_TEXT, "-\n\n\n\n",
	XeSTRING_COMPONENT_UNKNOWN);
   if(LXeStr) { EXtSetArg(XeNlabelString, LXeStr); }
   EXtSetArg(XeNheightAdjustable, True);
   _ModelNameLabelW=XtCreateManagedWidget("ModelNamesLabelW", xeLabelWidgetClass, LPanelW, Args, ArgCnt);
   if(LXeStr) XeStringFree(LXeStr);
   if(LXeStr1) XeStringFree(LXeStr1);



   E_KeySetCallback("Scale", (unsigned short)'x', 0, EKeyPRESS, E3dpKCB_Transform, (EPointer)E3dCR_SCALE);
   E_KeySetCallback("Rotate", (unsigned short)'c', 0, EKeyPRESS, E3dpKCB_Transform, (EPointer)E3dCR_ROTATE);
   E_KeySetCallback("Translate", (unsigned short)'v', 0, EKeyPRESS, E3dpKCB_Transform, (EPointer)E3dCR_TRANSLATE);

   E3dp_TransformPanelUpdate(E3dDO_ALL, E3dDO_ALL, E3dDO_ALL, TRUE);

   E3d_SceneAddCallback(E3d_Scene, E3dCALLBACK_ADDREMOVE, _CB_SceneAddRemove, (EPointer)0);
  }
 }
 if(LSwitch) XtManageChild(E3dp_TransformPanelW);
 else XtUnmanageChild(E3dp_TransformPanelW);
}


//================================================================================
// Initialize single-point moving:
//
// - Calculate the coefficients of the plane that is perpendicular to the
//   primary ray cast through the pointer's position and goes through the
//   point to be moved.
//
// This function expects world coordinates.
//================================================================================
void E3dp_InitPointMove(E3dWindow* L3DWindow, int LPtrX, int LPtrY, E3dCoordinate LX, E3dCoordinate LY, E3dCoordinate LZ, EBool LStoreForUndo)
{
 E3dCamera*	LCamera;
 E3dRay		LRay;


 if((LCamera=E3d_WindowCastPrimaryRay(L3DWindow, LPtrX, LPtrY, &LRay))!=NULL)
 {
// Intersect the ray with a plane that is perpendicular to the Camera's
// view direction and passes through the point to be moved
//
  E3dCoordinate	LD,
		LCDX, LCDY, LCDZ,				// Camera Direction vector
		LROX=LRay.X, LROY=LRay.Y, LROZ=LRay.Z,
		LRDX=LRay.DirX, LRDY=LRay.DirY, LRDZ=LRay.DirZ,
		LT;						// t parameter


// Get D in the plane equation
//
  LCDX=LCamera->Direction.X;
  LCDY=LCamera->Direction.Y;
  LCDZ=LCamera->Direction.Z;
  LD=-(LCDX*LX+LCDY*LY+LCDZ*LZ);

// t is the parameter for the ray where it intersects the plane
//
  LT=-(LCDX*LROX+LCDY*LROY+LCDZ*LROZ+LD)/(LCDX*LRDX+LCDY*LRDY+LCDZ*LRDZ);

  E3dp_PointMovePlaneCDX=LCDX;
  E3dp_PointMovePlaneCDY=LCDY;
  E3dp_PointMovePlaneCDZ=LCDZ;
  E3dp_PointMovePlaneDCoefficient=LD;

  E3dp_ActivePointMoveOffset.X=LX-(LROX+LT*LRDX);
  E3dp_ActivePointMoveOffset.Y=LY-(LROY+LT*LRDY);
  E3dp_ActivePointMoveOffset.Z=LZ-(LROZ+LT*LRDZ);

  E3dp_ActivePointOriginalPos.X=LX;
  E3dp_ActivePointOriginalPos.Y=LY;
  E3dp_ActivePointOriginalPos.Z=LZ;


  if(LStoreForUndo) E3dp_PushOperatorsForUndo=TRUE;
  else E3d_CurrentOperator=NULL;			// Make sure there's no current operator
 }
}


//================================================================================================
// Determine DeltaX, DeltaY and DeltaZ from LPtrXDiff and LPtrYDiff for the given 3DWindow
//================================================================================================
void E3dp_WindowComputeTranslation(E3dWindow* L3DWindow, E3dCoordinate LPtrXDiff, E3dCoordinate LPtrYDiff, unsigned int LTranslateAxes, E3d3DPosition* LDelta)
{
 E3dCamera*	LCamera;
 E3dMatrix	LMatrix;
 E3dCoordinate	LDiffFact;


 switch(L3DWindow->ViewMode)
 {
  case E3dVM_PERSPECTIVE:
   {
    E3dCoordinate	mX, mY, mZ;

    LCamera=&(L3DWindow->PerspectiveCamera);
    E3d_MatrixInvert3x3(LCamera->WorldToViewerMatrix, LMatrix);
    LDiffFact=LCamera->ViewDistance*2.0*sin(LCamera->FieldOfView*0.5*E3dDEGREES_TO_RADIANS)/L3DWindow->YSize;

    mX=LPtrXDiff*LDiffFact;mY=LPtrYDiff*LDiffFact;mZ=0.0;
    E3dM_MatrixTransform3x3(LMatrix, LDelta->X, LDelta->Y, LDelta->Z);
    LDelta->X*=1.0;
    LDelta->Y*=1.0;
    LDelta->Z*=1.0;
   }
  break;

  case E3dVM_TOP:
   LCamera=&(L3DWindow->TopCamera);
   LDiffFact=LCamera->XZoom*2.0/((float)L3DWindow->XSize);

   LTranslateAxes&=(E3dDO_X|E3dDO_Z);
   LDelta->X=LPtrXDiff*LDiffFact;
   LDelta->Y=0.0;
   LDelta->Z=-LPtrYDiff*LDiffFact;
  break;

  case E3dVM_FRONT:
   LCamera=&(L3DWindow->FrontCamera);
   LDiffFact=LCamera->XZoom*2.0/((float)L3DWindow->XSize);

   LTranslateAxes&=(E3dDO_X|E3dDO_Y);
   LDelta->X=LPtrXDiff*LDiffFact;
   LDelta->Y=LPtrYDiff*LDiffFact;
   LDelta->Z=0.0;
  break;

  case E3dVM_RIGHT:
   LCamera=&(L3DWindow->RightCamera);
   LDiffFact=LCamera->XZoom*2.0/((float)L3DWindow->XSize);

   LTranslateAxes&=(E3dDO_Z|E3dDO_Y);
   LDelta->X=0.0;
   LDelta->Y=LPtrYDiff*LDiffFact;
   LDelta->Z=-LPtrXDiff*LDiffFact;
  break;

  case E3dVM_SCHEMATICS:
   LCamera=&(L3DWindow->SchematicsCamera);
   LDiffFact=LCamera->XZoom*2.0/((float)L3DWindow->XSize);

   LTranslateAxes&=(E3dDO_X|E3dDO_Y);
   LDelta->X=LPtrXDiff*LDiffFact;
   LDelta->Y=LPtrYDiff*LDiffFact;
   LDelta->Z=0.0;
  break;
 }
}


//========================================================================================
// Move the given sub-CV of a Bezier curve and follow constraints
//========================================================================================
EBool E3d_MoveBezierCV(E3dBezierCV* LBezierCV, int LSubKey, E3dCoordinate LX, E3dCoordinate LY, E3dCoordinate LZ)
{
 EBool	LChanged=FALSE;


 switch(LSubKey)
 {
  case E3dBEZ_PREVIOUS:
   if(LBezierCV->Previous.X!=LX) { LBezierCV->Previous.X=LX;LChanged=TRUE; }
   if(LBezierCV->Previous.Y!=LY) { LBezierCV->Previous.Y=LY;LChanged=TRUE; }
   if(LBezierCV->Previous.Z!=LZ) { LBezierCV->Previous.Z=LZ;LChanged=TRUE; }

   if(LChanged)
   {
//printf("Constr %d\n", LBezierCV->Constraint);fflush(stdout);
    switch(LBezierCV->Constraint)
    {
     case E3dBEZ_C1:		// Mirror sub-CV position to the other sub-CV
      {
       E3dCoordinate	LDX=LBezierCV->Position.X-LBezierCV->Previous.X,
			LDY=LBezierCV->Position.Y-LBezierCV->Previous.Y,
			LDZ=LBezierCV->Position.Z-LBezierCV->Previous.Z;


       LBezierCV->Next.X=LBezierCV->Position.X+LDX;
       LBezierCV->Next.Y=LBezierCV->Position.Y+LDY;
       LBezierCV->Next.Z=LBezierCV->Position.Z+LDZ;
      }
     break;

     case E3dBEZ_G1:		// Mirror sub-CV direction, but maintain the lenght of the other handle
      {
       E3dCoordinate	LDX=LBezierCV->Position.X-LBezierCV->Previous.X,
			LDY=LBezierCV->Position.Y-LBezierCV->Previous.Y,
			LDZ=LBezierCV->Position.Z-LBezierCV->Previous.Z,
			LR=sqrt(LDX*LDX+LDY*LDY+LDZ*LDZ);

// Get unit-direction of Previous -> Position
//
       if(LR>0.0)
       {
	E3dCoordinate	LDX1=LBezierCV->Next.X-LBezierCV->Position.X,
			LDY1=LBezierCV->Next.Y-LBezierCV->Position.Y,
			LDZ1=LBezierCV->Next.Z-LBezierCV->Position.Z;

	LR=1.0/LR;
	LDX*=LR;
	LDY*=LR;
	LDZ*=LR;

	LR=sqrt(LDX1*LDX1+LDY1*LDY1+LDZ1*LDZ1);

	if(LR>0.0)
	{
	 LBezierCV->Next.X=LBezierCV->Position.X+LDX*LR;
	 LBezierCV->Next.Y=LBezierCV->Position.Y+LDY*LR;
	 LBezierCV->Next.Z=LBezierCV->Position.Z+LDZ*LR;
	}
       }
      }
     break;
    }
   }
  break;

  case E3dBEZ_POSITION:
   {
    E3dCoordinate	LDX=LX-LBezierCV->Position.X,
			LDY=LY-LBezierCV->Position.Y,
			LDZ=LZ-LBezierCV->Position.Z;


    if(LBezierCV->Position.X!=LX) { LBezierCV->Position.X=LX;LChanged=TRUE; }
    if(LBezierCV->Position.Y!=LY) { LBezierCV->Position.Y=LY;LChanged=TRUE; }
    if(LBezierCV->Position.Z!=LZ) { LBezierCV->Position.Z=LZ;LChanged=TRUE; }

// Move Previous and Next along...
//
    if(LChanged)
    {
     LBezierCV->Previous.X+=LDX;
     LBezierCV->Previous.Y+=LDY;
     LBezierCV->Previous.Z+=LDZ;
     LBezierCV->Next.X+=LDX;
     LBezierCV->Next.Y+=LDY;
     LBezierCV->Next.Z+=LDZ;
    }
   }
  break;

  case E3dBEZ_NEXT:
   if(LBezierCV->Next.X!=LX) { LBezierCV->Next.X=LX;LChanged=TRUE; }
   if(LBezierCV->Next.Y!=LY) { LBezierCV->Next.Y=LY;LChanged=TRUE; }
   if(LBezierCV->Next.Z!=LZ) { LBezierCV->Next.Z=LZ;LChanged=TRUE; }

   if(LChanged)
   {
    switch(LBezierCV->Constraint)
    {
     case E3dBEZ_C1:
      {
       E3dCoordinate	LDX=LBezierCV->Position.X-LBezierCV->Next.X,
			LDY=LBezierCV->Position.Y-LBezierCV->Next.Y,
			LDZ=LBezierCV->Position.Z-LBezierCV->Next.Z;


       LBezierCV->Previous.X=LBezierCV->Position.X+LDX;
       LBezierCV->Previous.Y=LBezierCV->Position.Y+LDY;
       LBezierCV->Previous.Z=LBezierCV->Position.Z+LDZ;
      }
     break;


     case E3dBEZ_G1:		// Mirror sub-CV direction, but maintain the lenght of the other handle
      {
       E3dCoordinate	LDX=LBezierCV->Position.X-LBezierCV->Next.X,
			LDY=LBezierCV->Position.Y-LBezierCV->Next.Y,
			LDZ=LBezierCV->Position.Z-LBezierCV->Next.Z,
			LR=sqrt(LDX*LDX+LDY*LDY+LDZ*LDZ);

// Get unit-direction of Next -> Position
//
       if(LR>0.0)
       {
	E3dCoordinate	LDX1=LBezierCV->Previous.X-LBezierCV->Position.X,
			LDY1=LBezierCV->Previous.Y-LBezierCV->Position.Y,
			LDZ1=LBezierCV->Previous.Z-LBezierCV->Position.Z;

	LR=1.0/LR;
	LDX*=LR;
	LDY*=LR;
	LDZ*=LR;

	LR=sqrt(LDX1*LDX1+LDY1*LDY1+LDZ1*LDZ1);

	if(LR>0.0)
	{
	 LBezierCV->Previous.X=LBezierCV->Position.X+LDX*LR;
	 LBezierCV->Previous.Y=LBezierCV->Position.Y+LDY*LR;
	 LBezierCV->Previous.Z=LBezierCV->Position.Z+LDZ*LR;
	}
       }
      }
     break;
    }
   }
  break;
 }

 return(LChanged);
}


//================================================================
// Determine along which axes we're allowed to translate
//================================================================
unsigned int E3dp_GetTranslateAxes(E3dWindow* L3DWindow, unsigned int LTranslateFlags, unsigned int LButtonState)
{
 unsigned int	LTranslateAxes=LTranslateFlags;


 if(LTranslateAxes)
 {
  switch(E3dp_TransformMode)
  {
   case E3dpTM_VIEW:
    if((LButtonState&Button1Mask)==0) LTranslateAxes=0;
   break;

   default:
    if((LButtonState&Button1Mask)==0) LTranslateAxes&=(E3dDO_ALL-E3dDO_X);
    if((LButtonState&Button2Mask)==0) LTranslateAxes&=(E3dDO_ALL-E3dDO_Y);
    if((LButtonState&Button3Mask)==0) LTranslateAxes&=(E3dDO_ALL-E3dDO_Z);
   break;
  }
 }

 switch(L3DWindow->ViewMode)
 {
  case E3dVM_TOP:
   LTranslateAxes&=(E3dDO_X|E3dDO_Z);
  break;

  case E3dVM_FRONT:
   LTranslateAxes&=(E3dDO_X|E3dDO_Y);
  break;

  case E3dVM_RIGHT:
   LTranslateAxes&=(E3dDO_Z|E3dDO_Y);
  break;
 }

 return(LTranslateAxes);
}


//========================================
// Interactive transformation handler
//========================================
void E3dp_MovePoint(E3dWindow* L3DWindow, int LPtrX, int LPtrY, unsigned int LButtonState)
{
 E3dGeometry*	LGeometry=E3dp_ActiveGeometry;
 E3dCamera*	LCamera;
 E3dRay		LRay;
 unsigned int	LTranslateAxes=E3dp_GetTranslateAxes(L3DWindow, E3dDO_ALL, LButtonState);
 EBool		LChanged=FALSE;


 if((LCamera=E3d_WindowCastPrimaryRay(L3DWindow, LPtrX, LPtrY, &LRay))!=NULL)
 {
// Intersect the ray with a plane that is perpendicular to the Camera's
// view direction and passes through the point's original position
//
  E3dWindowSettings*	L3DWindowSettings=&(L3DWindow->Settings);
  E3d3DPosition		LDelta;
  E3dCoordinate		LX, LY, LZ, LD,
			LROX=LRay.X, LROY=LRay.Y, LROZ=LRay.Z,
			LRDX=LRay.DirX, LRDY=LRay.DirY, LRDZ=LRay.DirZ,
			LT,						// t parameter
			mX, mY, mZ;


// Get D in the plane equation
//
  LD=E3dp_PointMovePlaneDCoefficient;

// t is the parameter for the ray where it intersects the plane
//
  LT=-(E3dp_PointMovePlaneCDX*LROX+E3dp_PointMovePlaneCDY*LROY+E3dp_PointMovePlaneCDZ*LROZ+LD)/(E3dp_PointMovePlaneCDX*LRDX+E3dp_PointMovePlaneCDY*LRDY+E3dp_PointMovePlaneCDZ*LRDZ);

  LX=LROX+LT*LRDX;
  LY=LROY+LT*LRDY;
  LZ=LROZ+LT*LRDZ;

  if(L3DWindowSettings->GridSnapToX) LX=E3d_SnapCoordinateTo(LX, L3DWindowSettings->GridSize.X);
  if(L3DWindowSettings->GridSnapToY) LY=E3d_SnapCoordinateTo(LY, L3DWindowSettings->GridSize.Y);
  if(L3DWindowSettings->GridSnapToZ) LZ=E3d_SnapCoordinateTo(LZ, L3DWindowSettings->GridSize.Z);

  if(LTranslateAxes&E3dDO_X) LDelta.X=LX-E3dp_ActivePointOriginalPos.X;else LDelta.X=0.0;
  if(LTranslateAxes&E3dDO_Y) LDelta.Y=LY-E3dp_ActivePointOriginalPos.Y;else LDelta.Y=0.0;
  if(LTranslateAxes&E3dDO_Z) LDelta.Z=LZ-E3dp_ActivePointOriginalPos.Z;else LDelta.Z=0.0;

  mX=LX;mY=LY;mZ=LZ;
  E3dM_MatrixTransform3x4(E3dp_WorldToLocalMatrix, LX, LY, LZ);

  if(E3dp_ActiveVertex)
  {
   E3dVertex*			LVertex=E3dp_ActiveVertex;
   E3dOperatorTranslateTags*	LOperator;


// Store Vertex position for undo
//
   if(E3dp_PushOperatorsForUndo)
   {
    E3dMeshVerticesSnapshot*	LDShapeSnapshot;
    E3dSnapshotVertex*		LSnapshotVertex;


    LOperator=(E3dOperatorTranslateTags*)EOp_OperatorAllocate("Moving Vertex", sizeof(E3dOperatorTranslateTags), E3dOp_TranslateTags);

    if((LDShapeSnapshot=(E3dMeshVerticesSnapshot*)EMalloc(sizeof(E3dMeshVerticesSnapshot)))!=NULL)
    {
     LOperator->TransformAxes=LTranslateAxes;
     LOperator->ShapeSnapshots=(E3dShapeSnapshot*)LDShapeSnapshot;
     LOperator->NumOfShapeSnapshots=1;

     LSnapshotVertex=(E3dSnapshotVertex*)EMalloc(sizeof(E3dSnapshotVertex));
     LSnapshotVertex->X=LVertex->X;
     LSnapshotVertex->Y=LVertex->Y;
     LSnapshotVertex->Z=LVertex->Z;
     LSnapshotVertex->Flags=LVertex->Flags;
     LSnapshotVertex->Index=LVertex-(E3dVertex*)(((E3dMesh*)E3dp_ActiveGeometry)->Vertices);

     LDShapeSnapshot->Mesh=(E3dMesh*)E3dp_ActiveGeometry;E3dp_ActiveGeometry->RefCnt+=1;
     LDShapeSnapshot->SnapshotVertices=LSnapshotVertex;
     LDShapeSnapshot->NumOfPoints=1;
     LDShapeSnapshot->PointSize=sizeof(LSnapshotVertex);

     E3dp_AppendOps1Operator((EOperator*)LOperator);
    }
    E3d_CurrentOperator=(EOperator*)LOperator;

    E3dp_PushOperatorsForUndo=FALSE;
   }
   else LOperator=(E3dOperatorTranslateTags*)E3d_CurrentOperator;

// Storing for undo, keep updating and calling the Operator
//
   if(LOperator)
   {
    LOperator->X=LDelta.X;
    LOperator->Y=LDelta.Y;
    LOperator->Z=LDelta.Z;
    E3dp_RefreshTranslationScales(LDelta.X, LDelta.Y, LDelta.Z, LTranslateAxes);
    E3dOp_TranslateTags((EOperator*)LOperator, EOpDO);

    E3dp_Redraw3DWindows(E3dDF_ALL, E3dVM_NORMAL3D);
   }
   else
   {
// Not storing for undo, do the move here
//
    if(LVertex->X!=LX) { LVertex->X=LX;LChanged=TRUE; }
    if(LVertex->Y!=LY) { LVertex->Y=LY;LChanged=TRUE; }
    if(LVertex->Z!=LZ) { LVertex->Z=LZ;LChanged=TRUE; }
    if(LChanged)
    {
//     E3d_MeshRefreshNormals((E3dMesh*)E3dp_ActiveGeometry, TRUE);

     E3d_GeometryUpdateForDisplay(E3dp_ActiveGeometry, E3dGF_SHAPE|E3dGF_NORMALS|E3dGF_REMAP_TEXTURES);

     E3dp_Refresh3DWindows(E3dDF_ALL, E3dVM_NORMAL3D);
    }
   }

   E3dp_PrintMessage(0, 0, "Vertex:  #%d  %.4f, %.4f, %.4f (local: %.4f, %.4f, %.4f)", E3dp_ActiveVertex-(((E3dMesh*)E3dp_ActiveGeometry)->Vertices), mX, mY, mZ, LVertex->X, LVertex->Y, LVertex->Z);
   return;
  }

  if(E3dp_ActiveBezierCV)
  {
   E3dSpline*			LSpline;
   E3dSplineCVsSnapshot*	LDShapeSnapshot;
   E3dBezierCV*			LBezierCV=E3dp_ActiveBezierCV;
   E3dOperatorTranslateTags*	LOperator;
   int				LCVIndex;


// Make sure E3dp_ActiveSpline is set
//
   if(E3dp_ActiveSpline==NULL)
   {
    switch(E3dp_ActiveGeometry->GeoType)
    {
     case E3dGEO_SPLINE:	E3dp_ActiveSpline=(E3dSpline*)E3dp_ActiveGeometry;break;
    }
   }
   LSpline=E3dp_ActiveSpline;
   LCVIndex=E3dp_ActiveBezierCV-(E3dBezierCV*)(LSpline->CVs);


// Store CV position for undo
//
   if(E3dp_PushOperatorsForUndo)
   {
    E3dSnapshotBezierCV*	LSnapshotCV;


    LOperator=(E3dOperatorTranslateTags*)EOp_OperatorAllocate("Moving Bezier CV", sizeof(E3dOperatorTranslateTags), E3dOp_TranslateTags);

    if((LDShapeSnapshot=(E3dSplineCVsSnapshot*)EMalloc(sizeof(E3dSplineCVsSnapshot)))!=NULL)
    {
     LSnapshotCV=(E3dSnapshotBezierCV*)EMalloc(sizeof(E3dSnapshotBezierCV));
     LSnapshotCV->Previous=LBezierCV->Previous;
     LSnapshotCV->Position=LBezierCV->Position;
     LSnapshotCV->Next=LBezierCV->Next;
     LSnapshotCV->Flags=LBezierCV->Flags;
     LSnapshotCV->Index=LCVIndex;

     LDShapeSnapshot->Spline=(E3dSpline*)E3dp_ActiveSpline;E3dp_ActiveSpline->RefCnt+=1;

     LDShapeSnapshot->SnapshotCVs=LSnapshotCV;
     LDShapeSnapshot->NumOfCVs=1;
     LDShapeSnapshot->CVSize=sizeof(LSnapshotCV);


     LOperator->TransformAxes=LTranslateAxes;

     switch(LGeometry->GeoType)
     {
      default:
       LOperator->ShapeSnapshots=(E3dShapeSnapshot*)LDShapeSnapshot;
       LOperator->NumOfShapeSnapshots=1;
      break;

      case E3dGEO_FACE:
       {
	E3dFaceCVsSnapshot*	LFaceShapeSnapshot;

	if((LFaceShapeSnapshot=(E3dFaceCVsSnapshot*)EMalloc(sizeof(E3dFaceCVsSnapshot)))!=NULL)
	{
	 LFaceShapeSnapshot->SplineCVsSnapshots=LDShapeSnapshot;
	 LFaceShapeSnapshot->NumOfSplineCVsSnapshots=1;

	 LFaceShapeSnapshot->Face=(E3dFace*)LGeometry;LGeometry->RefCnt+=1;

	 LOperator->ShapeSnapshots=(E3dShapeSnapshot*)LFaceShapeSnapshot;
	 LOperator->NumOfShapeSnapshots=1;
	}
       }
      break;
     }

     E3dp_AppendOps1Operator((EOperator*)LOperator);
    }
    E3d_CurrentOperator=(EOperator*)LOperator;

    E3dp_PushOperatorsForUndo=FALSE;
   }
   else LOperator=(E3dOperatorTranslateTags*)E3d_CurrentOperator;


// Storing for undo: keep updating and calling the Operator
//
   if(LOperator)
   {
    switch(LGeometry->GeoType)
    {
     default:
      LDShapeSnapshot=(E3dSplineCVsSnapshot*)(LOperator->ShapeSnapshots);
     break;

     case E3dGEO_FACE:
      {
       E3dFaceCVsSnapshot*	LFaceShapeSnapshot=(E3dFaceCVsSnapshot*)(LOperator->ShapeSnapshots);

       LDShapeSnapshot=LFaceShapeSnapshot->SplineCVsSnapshots;
      }
     break;
    }

    LOperator->X=LDelta.X;
    LOperator->Y=LDelta.Y;
    LOperator->Z=LDelta.Z;
    LDShapeSnapshot->SubCV=E3dp_BezCVActiveSubCV;
    E3dp_RefreshTranslationScales(LDelta.X, LDelta.Y, LDelta.Z, LTranslateAxes);

    E3dOp_TranslateTags((EOperator*)LOperator, EOpDO);
    E3dp_Redraw3DWindows(E3dDF_ALL, E3dVM_NORMAL3D);
   }
   else
   {
// Not storing for undo: do the move here
//
    if(E3d_MoveBezierCV(LBezierCV, E3dp_BezCVActiveSubCV, LX, LY, LZ))
    {
     E3d_GeometryUpdateForDisplay(LGeometry, E3dGF_SHAPE|E3dGF_REMAP_TEXTURES);
     E3dp_Redraw3DWindows(E3dDF_ALL, E3dVM_NORMAL3D);
    }
   }

   switch(E3dp_BezCVActiveSubCV)
   {
    case E3dBEZ_PREVIOUS:
     E3dp_PrintMessage(0, 0, "Point #%d (previous):  %.4f, %.4f, %.4f (local: %.4f, %.4f, %.4f)", LCVIndex, mX, mY, mZ, LBezierCV->Previous.X, LBezierCV->Previous.Y, LBezierCV->Previous.Z);
    break;

    case E3dBEZ_POSITION:
     E3dp_PrintMessage(0, 0, "Point #%d:  %.4f, %.4f, %.4f (local: %.4f, %.4f, %.4f)", LCVIndex, mX, mY, mZ, LBezierCV->Position.X, LBezierCV->Position.Y, LBezierCV->Position.Z);
    break;

    case E3dBEZ_NEXT:
     E3dp_PrintMessage(0, 0, "Point #%d (next):  %.4f, %.4f, %.4f (local: %.4f, %.4f, %.4f)", LCVIndex, mX, mY, mZ, LBezierCV->Next.X, LBezierCV->Next.Y, LBezierCV->Next.Z);
    break;
   }

   return;
  }

  if(E3dp_ActiveSplineCV)
  {
   E3dSpline*			LSpline;
   E3dSplineCV*			LSplineCV=E3dp_ActiveSplineCV;
   E3dOperatorTranslateTags*	LOperator;
   int				LCVIndex;


// Make sure E3dp_ActiveSpline is set
//
   if(E3dp_ActiveSpline==NULL)
   {
    switch(E3dp_ActiveGeometry->GeoType)
    {
     case E3dGEO_SPLINE:	E3dp_ActiveSpline=(E3dSpline*)E3dp_ActiveGeometry;break;
    }
   }
   LSpline=E3dp_ActiveSpline;
   LCVIndex=E3dp_ActiveSplineCV-(E3dSplineCV*)(LSpline->CVs);


// Store CV position for undo
//
   if(E3dp_PushOperatorsForUndo)
   {
    E3dSplineCVsSnapshot*	LDShapeSnapshot;
    E3dSnapshotSplineCV*	LSnapshotCV;


    LOperator=(E3dOperatorTranslateTags*)EOp_OperatorAllocate("Moving Spline CV", sizeof(E3dOperatorTranslateTags), E3dOp_TranslateTags);

    if((LDShapeSnapshot=(E3dSplineCVsSnapshot*)EMalloc(sizeof(E3dSplineCVsSnapshot)))!=NULL)
    {
     LSnapshotCV=(E3dSnapshotSplineCV*)EMalloc(sizeof(E3dSnapshotSplineCV));
     LSnapshotCV->Position=LSplineCV->Position;
     LSnapshotCV->Flags=LSplineCV->Flags;
     LSnapshotCV->Index=LCVIndex;

     LDShapeSnapshot->Spline=(E3dSpline*)E3dp_ActiveSpline;E3dp_ActiveSpline->RefCnt+=1;

     LDShapeSnapshot->SnapshotCVs=LSnapshotCV;
     LDShapeSnapshot->NumOfCVs=1;
     LDShapeSnapshot->CVSize=sizeof(LSnapshotCV);


     LOperator->TransformAxes=LTranslateAxes;

     switch(LGeometry->GeoType)
     {
      default:
       LOperator->ShapeSnapshots=(E3dShapeSnapshot*)LDShapeSnapshot;
       LOperator->NumOfShapeSnapshots=1;
      break;

      case E3dGEO_FACE:
       {
	E3dFaceCVsSnapshot*	LFaceShapeSnapshot;

	if((LFaceShapeSnapshot=(E3dFaceCVsSnapshot*)EMalloc(sizeof(E3dFaceCVsSnapshot)))!=NULL)
	{
	 LFaceShapeSnapshot->SplineCVsSnapshots=LDShapeSnapshot;
	 LFaceShapeSnapshot->NumOfSplineCVsSnapshots=1;

	 LFaceShapeSnapshot->Face=(E3dFace*)LGeometry;LGeometry->RefCnt+=1;

	 LOperator->ShapeSnapshots=(E3dShapeSnapshot*)LFaceShapeSnapshot;
	 LOperator->NumOfShapeSnapshots=1;
	}
       }
      break;
     }

     E3dp_AppendOps1Operator((EOperator*)LOperator);
    }
    E3d_CurrentOperator=(EOperator*)LOperator;

    E3dp_PushOperatorsForUndo=FALSE;
   }
   else LOperator=(E3dOperatorTranslateTags*)E3d_CurrentOperator;


// Storing for undo: keep updating and calling the Operator
//
   if(LOperator)
   {
    LOperator->X=LDelta.X;
    LOperator->Y=LDelta.Y;
    LOperator->Z=LDelta.Z;
    E3dp_RefreshTranslationScales(LDelta.X, LDelta.Y, LDelta.Z, LTranslateAxes);

    E3dOp_TranslateTags((EOperator*)LOperator, EOpDO);
    E3dp_Redraw3DWindows(E3dDF_ALL, E3dVM_NORMAL3D);
   }
   else
   {
// Not storing for undo: do the move here
//
    if(LSplineCV->Position.X!=LX) { LSplineCV->Position.X=LX;LChanged=TRUE; }
    if(LSplineCV->Position.Y!=LY) { LSplineCV->Position.Y=LY;LChanged=TRUE; }
    if(LSplineCV->Position.Z!=LZ) { LSplineCV->Position.Z=LZ;LChanged=TRUE; }

    if(LChanged)
    {
     E3d_GeometryUpdateForDisplay(E3dp_ActiveGeometry, E3dGF_SHAPE|E3dGF_NORMALS|E3dGF_REMAP_TEXTURES);

     E3dp_Refresh3DWindows(E3dDF_ALL, E3dVM_NORMAL3D);
    }
   }

   E3dp_PrintMessage(0, 0, "Point #%d: %.4f, %.4f, %.4f (local: %.4f, %.4f, %.4f)", LCVIndex, mX, mY, mZ, LSplineCV->Position.X, LSplineCV->Position.Y, LSplineCV->Position.Z);

   return;
  }


 }
}


static void _ScaleSplineTags(E3dSpline* LSpline, E3dSplineCVsSnapshot* LSplineCVsSnapshot, unsigned int LScaleAxes, E3dCoordinate LScaleX, E3dCoordinate LScaleY, E3dCoordinate LScaleZ)
{
 unsigned int		LVC=LSplineCVsSnapshot->NumOfCVs;

 switch(LSpline->SplineType)
 {
  case E3dSPL_BEZIER:
   {
    E3dSnapshotBezierCV*	LSnapshotBezierCV=(E3dSnapshotBezierCV*)(LSplineCVsSnapshot->SnapshotCVs);
    E3dBezierCV*		LBezierCVs=(E3dBezierCV*)(LSpline->CVs);
    E3dBezierCV*		LBezierCV;

    do
    {
     LBezierCV=LBezierCVs+LSnapshotBezierCV->Index;

     if(LScaleAxes&E3dDO_X)
     {
      LBezierCV->Position.X=LSnapshotBezierCV->Position.X*LScaleX;
      LBezierCV->Previous.X=LSnapshotBezierCV->Previous.X*LScaleX;
      LBezierCV->Next.X=LSnapshotBezierCV->Next.X*LScaleX;
     }

     if(LScaleAxes&E3dDO_Y)
     {
      LBezierCV->Position.Y=LSnapshotBezierCV->Position.Y*LScaleY;
      LBezierCV->Previous.Y=LSnapshotBezierCV->Previous.Y*LScaleY;
      LBezierCV->Next.Y=LSnapshotBezierCV->Next.Y*LScaleY;
     }

     if(LScaleAxes&E3dDO_Z)
     {
      LBezierCV->Position.Z=LSnapshotBezierCV->Position.Z*LScaleZ;
      LBezierCV->Previous.Z=LSnapshotBezierCV->Previous.Z*LScaleZ;
      LBezierCV->Next.Z=LSnapshotBezierCV->Next.Z*LScaleZ;
     }
     LSnapshotBezierCV++;
    } while(--LVC);
   }
  break;

  default:
   {
    E3dSnapshotSplineCV*	LSnapshotSplineCV=(E3dSnapshotSplineCV*)(LSplineCVsSnapshot->SnapshotCVs);
    E3dSplineCV*		LSplineCVs=(E3dSplineCV*)(LSpline->CVs);
    E3dSplineCV*		LSplineCV;

    do
    {
     LSplineCV=LSplineCVs+LSnapshotSplineCV->Index;

     if(LScaleAxes&E3dDO_X) LSplineCV->Position.X=LSnapshotSplineCV->Position.X*LScaleX;
     if(LScaleAxes&E3dDO_Y) LSplineCV->Position.Y=LSnapshotSplineCV->Position.Y*LScaleY;
     if(LScaleAxes&E3dDO_Z) LSplineCV->Position.Z=LSnapshotSplineCV->Position.Z*LScaleZ;
     LSnapshotSplineCV++;
    } while(--LVC);
   }
  break;
 }
}


void _UnTransformSplineTags(E3dSpline* LSpline, E3dSplineCVsSnapshot* LSplineCVsSnapshot, unsigned int LAxes)
{
 unsigned int		LVC=LSplineCVsSnapshot->NumOfCVs;

 switch(LSpline->SplineType)
 {
  case E3dSPL_BEZIER:
   {
    E3dSnapshotBezierCV*	LSnapshotBezierCV=(E3dSnapshotBezierCV*)(LSplineCVsSnapshot->SnapshotCVs);
    E3dBezierCV*		LBezierCVs=(E3dBezierCV*)(LSpline->CVs);
    E3dBezierCV*		LBezierCV;

    do
    {
     LBezierCV=LBezierCVs+LSnapshotBezierCV->Index;

     if(LAxes&E3dDO_X)
     {
      LBezierCV->Position.X=LSnapshotBezierCV->Position.X;
      LBezierCV->Previous.X=LSnapshotBezierCV->Previous.X;
      LBezierCV->Next.X=LSnapshotBezierCV->Next.X;
     }

     if(LAxes&E3dDO_Y)
     {
      LBezierCV->Position.Y=LSnapshotBezierCV->Position.Y;
      LBezierCV->Previous.Y=LSnapshotBezierCV->Previous.Y;
      LBezierCV->Next.Y=LSnapshotBezierCV->Next.Y;
     }

     if(LAxes&E3dDO_Z)
     {
      LBezierCV->Position.Z=LSnapshotBezierCV->Position.Z;
      LBezierCV->Previous.Z=LSnapshotBezierCV->Previous.Z;
      LBezierCV->Next.Z=LSnapshotBezierCV->Next.Z;
     }
     LSnapshotBezierCV++;
    } while(--LVC);
   }
  break;

  default:
   {
    E3dSnapshotSplineCV*	LSnapshotSplineCV=(E3dSnapshotSplineCV*)(LSplineCVsSnapshot->SnapshotCVs);
    E3dSplineCV*		LSplineCVs=(E3dSplineCV*)(LSpline->CVs);
    E3dSplineCV*		LSplineCV;

    do
    {
     LSplineCV=LSplineCVs+LSnapshotSplineCV->Index;

     if(LAxes&E3dDO_X) LSplineCV->Position.X=LSnapshotSplineCV->Position.X;
     if(LAxes&E3dDO_Y) LSplineCV->Position.Y=LSnapshotSplineCV->Position.Y;
     if(LAxes&E3dDO_Z) LSplineCV->Position.Z=LSnapshotSplineCV->Position.Z;
     LSnapshotSplineCV++;
    } while(--LVC);
   }
  break;
 }
}



#define _M_ModelOperatorFree()\
   if(LN)\
   {\
    for(LC=0;LC<LN;LC++) E3d_ModelFree(LModels[LC]);\
    EFree(LOperator->Models);LOperator->Models=NULL;LOperator->NumOfModels=0;\
    EFree(LOperator->TransformBases);LOperator->TransformBases=NULL;\
   }



//================================================
// Scale Models Operator proc
//================================================
unsigned int E3dOp_ScaleModels(EOperator* LOperatorIn, int LOperation)
{
 E3dOperatorScaleModels*	LOperator=(E3dOperatorScaleModels*)LOperatorIn;
 E3dModel**			LModels=LOperator->Models;
 E3dModel*			LModel;
 unsigned int			LC, LN=LOperator->NumOfModels, LTransformAxes=LOperator->TransformAxes;
 unsigned int			LChanged=0;

 switch(LOperation)
 {
  case EOpDO:
   {
    E3dSceneModelsCallbackStruct	LCBS;
    E3dModel**				LTransformedModelRoots=NULL;
    E3d3DPosition*			LTransformBase=LOperator->TransformBases;
    E3dCoordinate			LScaleX=LOperator->X, LScaleY=LOperator->Y, LScaleZ=LOperator->Z;
    unsigned int			LNumOfTransformedModelRoots=0;
    EBool				LChg;


    for(LC=0;LC<LN;LC++, LTransformBase++)
    {
     LModel=LModels[LC];

     LChg=FALSE;

     if(LTransformAxes&E3dDO_X)
     {
      LModel->Scaling.X=LTransformBase->X*LScaleX;
      LChg=TRUE;LChanged|=E3dCHG_TRANSFORM;
     }

     if(LTransformAxes&E3dDO_Y)
     {
      LModel->Scaling.Y=LTransformBase->Y*LScaleY;
      LChg=TRUE;LChanged|=E3dCHG_TRANSFORM;
     }

     if(LTransformAxes&E3dDO_Z)
     {
      LModel->Scaling.Z=LTransformBase->Z*LScaleZ;
      LChg=TRUE;LChanged|=E3dCHG_TRANSFORM;
     }

// Collect a non-repeating list of root Models of hierarchies that have selected Models
//
     if(LChg)
     {
      while(LModel->Parent) LModel=LModel->Parent;
      ELst_AddPointerChk((void***)(&LTransformedModelRoots), &LNumOfTransformedModelRoots, LModel);
     }
    }

    if(LChanged)
    {
     for(LC=0;LC<LNumOfTransformedModelRoots;LC++)
     {
      LModel=LTransformedModelRoots[LC];
      E3d_ModelHrcRefreshMatrices(LModel);
//     if(E3d_GLDataTransformed) E3d_ModelHrcRefreshForDisplay(LModel, FALSE);
     }
     if(LTransformedModelRoots) EFree(LTransformedModelRoots);

     LCBS.Reason=E3dCR_NODE_SCALE;
     LCBS.Models=LModels;
     LCBS.NumOfModels=LN;
     E3d_CallCallbacks(E3d_Scene, E3d_Scene->Callbacks, E3d_Scene->NumOfCallbacks, &LCBS);

     LModel=LModels[0];
     E3dp_RefreshScalingScales(LModel->Scaling.X, LModel->Scaling.Y, LModel->Scaling.Z, LTransformAxes);
     return(LChanged);
    }
   }
  break;

  case EOpUNDO:
   {
    E3dSceneModelsCallbackStruct	LCBS;
    E3dModel**				LTransformedModelRoots=NULL;
    E3d3DPosition*			LTransformBase=LOperator->TransformBases;
    unsigned int			LNumOfTransformedModelRoots=0;
    EBool				LChg;


    for(LC=0;LC<LN;LC++, LTransformBase++)
    {
     LModel=LModels[LC];

     LChg=FALSE;
     if(LTransformAxes&E3dDO_X)
     {
      if(LModel->Scaling.X!=LTransformBase->X) { LModel->Scaling.X=LTransformBase->X;LChg=TRUE;LChanged|=E3dCHG_TRANSFORM; }
     }

     if(LTransformAxes&E3dDO_Y)
     {
      if(LModel->Scaling.Y!=LTransformBase->Y) { LModel->Scaling.Y=LTransformBase->Y;LChg=TRUE;LChanged|=E3dCHG_TRANSFORM; }
     }

     if(LTransformAxes&E3dDO_Z)
     {
      if(LModel->Scaling.Z!=LTransformBase->Z) { LModel->Scaling.Z=LTransformBase->Z;LChg=TRUE;LChanged|=E3dCHG_TRANSFORM; }
     }

// Collect a non-repeating list of root Models of hierarchies that have selected Models
//
     if(LChg)
     {
      while(LModel->Parent) LModel=LModel->Parent;
      ELst_AddPointerChk((void***)(&LTransformedModelRoots), &LNumOfTransformedModelRoots, LModel);
     }
    }

    if(LChanged)
    {
     for(LC=0;LC<LNumOfTransformedModelRoots;LC++)
     {
      LModel=LTransformedModelRoots[LC];
      E3d_ModelHrcRefreshMatrices(LModel);
//     if(E3d_GLDataTransformed) E3d_ModelHrcRefreshForDisplay(LModel, FALSE);
     }
     if(LTransformedModelRoots) EFree(LTransformedModelRoots);


     LCBS.Reason=E3dCR_NODE_SCALE;
     LCBS.Models=LModels;
     LCBS.NumOfModels=LN;
     E3d_CallCallbacks(E3d_Scene, E3d_Scene->Callbacks, E3d_Scene->NumOfCallbacks, &LCBS);

     LModel=LModels[0];
     E3dp_RefreshScalingScales(LModel->Scaling.X, LModel->Scaling.Y, LModel->Scaling.Z, LTransformAxes);

     return(LChanged);
    }
   }
  break;

  case EOpFREE:
   _M_ModelOperatorFree();
  break;
 }
 return(0);
}


//================================================
// Scale tags Operator proc
//================================================
unsigned int E3dOp_ScaleTags(EOperator* LOperatorIn, int LOperation)
{
 E3dOperatorScaleTags*	LOperator=(E3dOperatorScaleTags*)LOperatorIn;
 E3dShapeSnapshot*	LShapeSnapshot=LOperator->ShapeSnapshots;
 E3dInterface**		LOutputs=NULL;
 unsigned int		LGmC, LShapeN=LOperator->NumOfShapeSnapshots, LOC, LNumOfOutputs=0, LNumOfOutputsAllocated=0;

 switch(LOperation)
 {
  case EOpDO:
   {
    E3dGeometry*		LGeometry;
    E3dGeometryCallbackStruct	LCBS;

    for(LGmC=0;LGmC<LShapeN;LGmC++, LShapeSnapshot++)
    {
     LGeometry=LShapeSnapshot->Geometry;
     switch(LGeometry->GeoType)
     {
      caseE3dMESH():
       {
	E3dSnapshotVertex*	LSnapshotVertex=((E3dMeshVerticesSnapshot*)LShapeSnapshot)->SnapshotVertices;
	E3dVertex*		LVertices=((E3dMesh*)LGeometry)->Vertices;
	E3dVertex*		LVertex;
	unsigned int		LVC=((E3dMeshVerticesSnapshot*)LShapeSnapshot)->NumOfPoints;

	do
	{
	 LVertex=LVertices+LSnapshotVertex->Index;
	 if(LOperator->TransformAxes&E3dDO_X) LVertex->X=LSnapshotVertex->X*LOperator->X;
	 if(LOperator->TransformAxes&E3dDO_Y) LVertex->Y=LSnapshotVertex->Y*LOperator->Y;
	 if(LOperator->TransformAxes&E3dDO_Z) LVertex->Z=LSnapshotVertex->Z*LOperator->Z;
	 LSnapshotVertex++;
	} while(--LVC);

	E3d_GeometryUpdateForDisplay(LGeometry, E3dGF_SHAPE|E3dGF_NORMALS|E3dGF_REMAP_TEXTURES);
       }
      break;

      case E3dGEO_SPLINE:
       {
	E3dSpline*		LSpline=(E3dSpline*)LGeometry;

	_ScaleSplineTags(LSpline, (E3dSplineCVsSnapshot*)LShapeSnapshot, LOperator->TransformAxes, LOperator->X, LOperator->Y, LOperator->Z);

	E3d_SplineCreateLinearSegments(LSpline);
	E3d_SplineUpdateSegmentLengths(LSpline, LSpline->NumOfStepsForLength);

// Collect Output interfaces into a non-repeating array, so for example the "Skin" tool's
// InputChanged method will only get called once, even if many of its Splines change
//
	for(LOC=0;LOC<(LSpline->NumOfOutputs);LOC++) ELst_AddPointerAChk((void***)(&LOutputs), &LNumOfOutputs, &LNumOfOutputsAllocated, 4, LSpline->Outputs[LOC]);
       }
      break;

      case E3dGEO_FACE:
       {
	E3dFace*		LFace=(E3dFace*)LGeometry;
	E3dSplineCVsSnapshot*	LCVsSnapshot=((E3dFaceCVsSnapshot*)LShapeSnapshot)->SplineCVsSnapshots;
	unsigned int		LSC, LNumOfSplines=((E3dFaceCVsSnapshot*)LShapeSnapshot)->NumOfSplineCVsSnapshots;

	for(LSC=0;LSC<LNumOfSplines;LSC++, LCVsSnapshot++)
	{
	 _ScaleSplineTags(LCVsSnapshot->Spline, LCVsSnapshot, LOperator->TransformAxes, LOperator->X, LOperator->Y, LOperator->Z);
	 E3d_SplineCreateLinearSegments(LCVsSnapshot->Spline);
	 E3d_SplineUpdateSegmentLengths(LCVsSnapshot->Spline, LCVsSnapshot->Spline->NumOfStepsForLength);
	}

// Free these and E3d_DrawFace() or E3d_GetRenderTriangles() etc. will call E3d_FaceTesselate() when necessary
//
// This will speed things up when there are no Windows with Solid display modes
//
	if(LFace->Vertices) { EFree(LFace->Vertices);LFace->Vertices=NULL;LFace->NumOfVertices=0; }
	if(LFace->Triangles) { EFree(LFace->Triangles);LFace->Triangles=NULL;LFace->NumOfTriangles=0; }

// Collect Output interfaces into a non-repeating array, so for example the "Skin" tool's
// InputChanged method will only get called once, even if many of its Splines change
//
	for(LOC=0;LOC<(LFace->NumOfOutputs);LOC++) ELst_AddPointerAChk((void***)(&LOutputs), &LNumOfOutputs, &LNumOfOutputsAllocated, 4, LFace->Outputs[LOC]);
       }
      break;
     }
    }

    if(LOutputs)
    {
     LCBS.Reasons=E3dGF_SHAPE|E3dGF_NORMALS|E3dGF_REMAP_TEXTURES;
     E3d_CallOutputs(NULL, LOutputs, LNumOfOutputs, &LCBS);
     EFree(LOutputs);
    }
    return(E3dCHG_SHAPE);
   }

  case EOpUNDO:
   _M_UndoTagTransform();

  case EOpFREE:
   if(LShapeN)
   {
    E3dShapeSnapshot*	LShapeSnapshot=LOperator->ShapeSnapshots;

    for(LGmC=0;LGmC<LShapeN;LGmC++, LShapeSnapshot++)
    {
     E3d_ShapeSnapshotFree(LShapeSnapshot);
    }
    EFree(LOperator->ShapeSnapshots);LOperator->ShapeSnapshots=NULL;LOperator->NumOfShapeSnapshots=0;
   }
  break;
 }
 return(0);
}


//========================================
// Interactive scaling
//========================================
static EBool _Scale(unsigned int LTransformAxes, int LPtrXDiff, int LPtrYDiff)
{
 E3dModel*	LModel;
 double		LPtrDiff;
 unsigned int	LC;
 EBool		LChanged=FALSE;


 if(E3dM_ABS(LPtrXDiff)>E3dM_ABS(LPtrYDiff)) LPtrDiff=(double)LPtrXDiff;
 else LPtrDiff=(double)LPtrYDiff;

 switch(E3dp_TransformTarget)
 {
  caseE3dpTT_OBJECT():
   {
    E3dOperatorScaleModels*	LOperator;
    E3d3DPosition*		LTransformBase;

// Store Model rotations for undo
//
    if(E3dp_PushOperatorsForUndo)
    {
     LOperator=(E3dOperatorScaleModels*)EOp_OperatorAllocate("Rotating Model(s)", sizeof(E3dOperatorScaleModels), E3dOp_ScaleModels);

     if((LOperator->Models=(E3dModel**)EMalloc(sizeof(E3dModel*)*E3dp_NumOfTransformedModels))!=NULL)
     {
      LOperator->TransformAxes=LTransformAxes;
      memcpy(LOperator->Models, E3dp_TransformedModels, sizeof(E3dModel*)*E3dp_NumOfTransformedModels);
      LOperator->NumOfModels=E3dp_NumOfTransformedModels;

      LOperator->TransformBases=(E3d3DPosition*)EMalloc(sizeof(E3d3DPosition)*E3dp_NumOfTransformedModels);
      LTransformBase=LOperator->TransformBases;

      for(LC=0;LC<E3dp_NumOfTransformedModels;LC++, LTransformBase++)
      {
       LModel=E3dp_TransformedModels[LC];
       LTransformBase->X=LModel->Scaling.X;
       LTransformBase->Y=LModel->Scaling.Y;
       LTransformBase->Z=LModel->Scaling.Z;
       LModel->RefCnt+=1;
      }

      E3dp_AppendOps1Operator((EOperator*)LOperator);
     }
     E3d_CurrentOperator=(EOperator*)LOperator;

     E3dp_PushOperatorsForUndo=FALSE;
    }
    else LOperator=(E3dOperatorScaleModels*)E3d_CurrentOperator;

    if(LTransformAxes&E3dDO_X) E3dp_Scaling.X=(E3dCoordinate)pow(1.01, (double)LPtrXDiff);
    if(LTransformAxes&E3dDO_Y) E3dp_Scaling.Y=(E3dCoordinate)pow(1.01, (double)LPtrYDiff);
    if(LTransformAxes&E3dDO_Z) E3dp_Scaling.Z=(E3dCoordinate)pow(1.01, (double)LPtrXDiff);

    LOperator->X=E3dp_Scaling.X;
    LOperator->Y=E3dp_Scaling.Y;
    LOperator->Z=E3dp_Scaling.Z;

    if(E3dOp_ScaleModels((EOperator*)LOperator, EOpDO)) LChanged=TRUE;
   }
  break;

  case E3dpTT_TAG:
   {
    E3dOperatorScaleTags*	LOperator;

// Store Point (Vertex, Spline CV etc.) positions for undo
//
    if(E3dp_PushOperatorsForUndo)
    {
     LOperator=(E3dOperatorScaleTags*)EOp_OperatorAllocate("Scaling tagged point(s)", sizeof(E3dOperatorScaleTags), E3dOp_ScaleTags);

     LOperator->TransformAxes=LTransformAxes;
     LOperator->ShapeSnapshots=E3dp_ShapeSnapshots;
     LOperator->NumOfShapeSnapshots=E3dp_NumOfShapeSnapshots;
     E3dp_ShapeSnapshots=NULL;E3dp_NumOfShapeSnapshots=0;
     E3dp_AppendOps1Operator((EOperator*)LOperator);

     E3d_CurrentOperator=(EOperator*)LOperator;

     E3dp_PushOperatorsForUndo=FALSE;
    }
    else LOperator=(E3dOperatorScaleTags*)E3d_CurrentOperator;

    if(LTransformAxes&E3dDO_X) E3dp_Scaling.X=(E3dCoordinate)pow(1.01, (double)LPtrXDiff);
    if(LTransformAxes&E3dDO_Y) E3dp_Scaling.Y=(E3dCoordinate)pow(1.01, (double)LPtrYDiff);
    if(LTransformAxes&E3dDO_Z) E3dp_Scaling.Z=(E3dCoordinate)pow(1.01, (double)LPtrXDiff);

    LOperator->X=E3dp_Scaling.X;
    LOperator->Y=E3dp_Scaling.Y;
    LOperator->Z=E3dp_Scaling.Z;
    E3dp_RefreshScalingScales(E3dp_Scaling.X, E3dp_Scaling.Y, E3dp_Scaling.Z, LTransformAxes);

    E3dOp_ScaleTags((EOperator*)LOperator, EOpDO);
    E3dp_Redraw3DWindows(E3dDF_ALL, E3dVM_NORMAL3D);
   }
  break;
 }
 return(LChanged);
}


static void _RotateSplineTags(E3dSpline* LSpline, E3dSplineCVsSnapshot* LSplineCVsSnapshot, E3dMatrix LMatrix)
{
 E3dCoordinate	mX, mY, mZ;
 unsigned int	LVC=LSplineCVsSnapshot->NumOfCVs;

 switch(LSpline->SplineType)
 {
  case E3dSPL_BEZIER:
   {
    E3dSnapshotBezierCV*	LSnapshotBezierCV=(E3dSnapshotBezierCV*)(LSplineCVsSnapshot->SnapshotCVs);
    E3dBezierCV*		LBezierCVs=(E3dBezierCV*)(LSpline->CVs);
    E3dBezierCV*		LBezierCV;

    do
    {
     LBezierCV=LBezierCVs+LSnapshotBezierCV->Index;

     mX=LSnapshotBezierCV->Position.X;
     mY=LSnapshotBezierCV->Position.Y;
     mZ=LSnapshotBezierCV->Position.Z;
     E3dM_MatrixTransform3x3(LMatrix, LBezierCV->Position.X, LBezierCV->Position.Y, LBezierCV->Position.Z);

     mX=LSnapshotBezierCV->Previous.X;
     mY=LSnapshotBezierCV->Previous.Y;
     mZ=LSnapshotBezierCV->Previous.Z;
     E3dM_MatrixTransform3x3(LMatrix, LBezierCV->Previous.X, LBezierCV->Previous.Y, LBezierCV->Previous.Z);

     mX=LSnapshotBezierCV->Next.X;
     mY=LSnapshotBezierCV->Next.Y;
     mZ=LSnapshotBezierCV->Next.Z;
     E3dM_MatrixTransform3x3(LMatrix, LBezierCV->Next.X, LBezierCV->Next.Y, LBezierCV->Next.Z);

     LSnapshotBezierCV++;
    } while(--LVC);
   }
  break;

  default:
   {
    E3dSnapshotSplineCV*	LSnapshotSplineCV=(E3dSnapshotSplineCV*)(LSplineCVsSnapshot->SnapshotCVs);
    E3dSplineCV*		LSplineCVs=(E3dSplineCV*)(LSpline->CVs);
    E3dSplineCV*		LSplineCV;

    do
    {
     LSplineCV=LSplineCVs+LSnapshotSplineCV->Index;

     mX=LSnapshotSplineCV->Position.X;
     mY=LSnapshotSplineCV->Position.Y;
     mZ=LSnapshotSplineCV->Position.Z;
     E3dM_MatrixTransform3x3(LMatrix, LSplineCV->Position.X, LSplineCV->Position.Y, LSplineCV->Position.Z);
     LSnapshotSplineCV++;
    } while(--LVC);
   }
  break;
 }
}


//================================================
// Rotate Models Operator proc
//================================================
unsigned int E3dOp_RotateModels(EOperator* LOperatorIn, int LOperation)
{
 E3dScene*			LScene=E3d_Scene;
 E3dSceneModelsCallbackStruct	LCBS;
 E3dOperatorRotateModels*	LOperator=(E3dOperatorRotateModels*)LOperatorIn;
 E3dModel**			LTransformedModelRoots=NULL;
 E3dJointModel**		LJointModels=NULL;
 E3dModel**			LModels=LOperator->Models;
 E3dModel*			LModel;
 E3dGeometry**			LAffectedSkins=NULL;
 unsigned int			LNumOfTransformedModelRoots=0,
				LNumOfJointModels=0,
				LNumOfAffectedSkins=0,
				LC, LN=LOperator->NumOfModels, LTransformAxes=LOperator->TransformAxes,
				LChanged=0;


 switch(LOperation)
 {
  case EOpDO:
   {
    E3dRotation*			LTransformBase=LOperator->TransformBases;
    E3dAngle				LRotX=LOperator->X, LRotY=LOperator->Y, LRotZ=LOperator->Z;
    EBool				LChg;


    for(LC=0;LC<LN;LC++, LTransformBase++)
    {
     LModel=LModels[LC];

     LChg=FALSE;

     if(LTransformAxes&E3dDO_X)
     {
      LModel->Rotation.X=LTransformBase->X+LRotX;
      if(LModel->Rotation.X>=360.0) LModel->Rotation.X-=360.0;
      else if(LModel->Rotation.X<0.0) LModel->Rotation.X+=360.0;
      LChg=TRUE;LChanged|=E3dCHG_TRANSFORM;
     }

     if(LTransformAxes&E3dDO_Y)
     {
      LModel->Rotation.Y=LTransformBase->Y+LRotY;
      if(LModel->Rotation.Y>=360.0) LModel->Rotation.Y-=360.0;
      else if(LModel->Rotation.Y<0.0) LModel->Rotation.Y+=360.0;
      LChg=TRUE;LChanged|=E3dCHG_TRANSFORM;
     }

     if(LTransformAxes&E3dDO_Z)
     {
      LModel->Rotation.Z=LTransformBase->Z+LRotZ;
      if(LModel->Rotation.Z>=360.0) LModel->Rotation.Z-=360.0;
      else if(LModel->Rotation.Z<0.0) LModel->Rotation.Z+=360.0;
      LChg=TRUE;LChanged|=E3dCHG_TRANSFORM;
     }

     if(LChg)
     {
      E3dJointModel*	LJointModel;
      E3dModel*		LModelAfterBranch=E3d_ModelHrcBranchGetNodeAfter(LModel);
      E3dModel*		LTModel=LModel;


// Collect a non-repeating list of affected Skin Geometries
//
      for(;LTModel!=LModelAfterBranch;LTModel=LTModel->Next)
      {
       if(LTModel->Type==E3dMDL_JOINT)
       {
	LJointModel=(E3dJointModel*)LTModel;

	ELst_AddPointerChk((void***)(&LJointModels), &LNumOfJointModels, LTModel);

	if(LJointModel->Skin)
	{
	 ELst_AddPointerChk((void***)(&LAffectedSkins), &LNumOfAffectedSkins, LJointModel->Skin);
	}
       }
      }


// Collect a non-repeating list of root Models of hierarchies that have selected Models
//
      while(LModel->Parent) LModel=LModel->Parent;
      ELst_AddPointerChk((void***)(&LTransformedModelRoots), &LNumOfTransformedModelRoots, LModel);
     }
    }
   }
  break;

  case EOpUNDO:
   {
    E3dRotation*	LTransformBase=LOperator->TransformBases;
    EBool		LChg;


    for(LC=0;LC<LN;LC++, LTransformBase++)
    {
     LModel=LModels[LC];

     LChg=FALSE;
     if(LTransformAxes&E3dDO_X)
     {
      if(LModel->Rotation.X!=LTransformBase->X) { LModel->Rotation.X=LTransformBase->X;LChg=TRUE;LChanged|=E3dCHG_TRANSFORM; }
     }

     if(LTransformAxes&E3dDO_Y)
     {
      if(LModel->Rotation.Y!=LTransformBase->Y) { LModel->Rotation.Y=LTransformBase->Y;LChg=TRUE;LChanged|=E3dCHG_TRANSFORM; }
     }

     if(LTransformAxes&E3dDO_Z)
     {
      if(LModel->Rotation.Z!=LTransformBase->Z) { LModel->Rotation.Z=LTransformBase->Z;LChg=TRUE;LChanged|=E3dCHG_TRANSFORM; }
     }


     if(LChg)
     {
      E3dJointModel*	LJointModel;
      E3dModel*		LModelAfterBranch=E3d_ModelHrcBranchGetNodeAfter(LModel);
      E3dModel*		LTModel=LModel;


// Collect a non-repeating list of affected Skin Geometries and JointModels
//
      for(;LTModel!=LModelAfterBranch;LTModel=LTModel->Next)
      {
       if(LTModel->Type==E3dMDL_JOINT)
       {
	LJointModel=(E3dJointModel*)LTModel;

	ELst_AddPointerChk((void***)(&LJointModels), &LNumOfJointModels, LTModel);

	if(LJointModel->Skin)
	{
	 ELst_AddPointerChk((void***)(&LAffectedSkins), &LNumOfAffectedSkins, LJointModel->Skin);
	}
       }
      }


// Collect a non-repeating list of root Models of hierarchies that have selected Models
//
      while(LModel->Parent) LModel=LModel->Parent;
      ELst_AddPointerChk((void***)(&LTransformedModelRoots), &LNumOfTransformedModelRoots, LModel);
     }
    }
   }
  break;

  case EOpFREE:
   _M_ModelOperatorFree();
  break;
 }



 if(LChanged)
 {
  for(LC=0;LC<LNumOfTransformedModelRoots;LC++)
  {
   LModel=LTransformedModelRoots[LC];
   E3d_ModelHrcRefreshMatrices(LModel);
//     if(E3d_GLDataTransformed) E3d_ModelHrcRefreshForDisplay(LModel, FALSE);
  }
  if(LTransformedModelRoots) EFree(LTransformedModelRoots);


  {
   E3dSkinMesh*	LSkinMesh;


// Update the SkinningMatrix of the affected JointModels
//
   if(LJointModels)
   {
    for(LC=0;LC<LNumOfJointModels;LC++)
    {
     E3d_JointModelUpdateSkinningMatrix(LJointModels[LC]);
    }
    EFree(LJointModels);
   }

// Mark the affected Skin Geometries that they need updating
//
   if(LAffectedSkins)
   {
    for(LC=0;LC<LNumOfAffectedSkins;LC++)
    {
     switch(LAffectedSkins[LC]->GeoType)
     {
      case E3dGEO_SKINMESH:
       LSkinMesh=(E3dSkinMesh*)(LAffectedSkins[LC]);
       LSkinMesh->UpdateCnt+=1;

       E3d_SkinMeshUpdateShape(LSkinMesh, TRUE);
      break;
     }
    }
    EFree(LAffectedSkins);
   }
  }

  LCBS.Reason=E3dCR_NODE_ROTATE;
  LCBS.Models=LModels;
  LCBS.NumOfModels=LN;
  E3d_CallCallbacks(LScene, LScene->Callbacks, LScene->NumOfCallbacks, &LCBS);

  LModel=LModels[0];
  E3dp_RefreshRotationScales(LModel->Rotation.X, LModel->Rotation.Y, LModel->Rotation.Z, LTransformAxes);
  return(LChanged);
 }



 return(LChanged);
}


//================================================
// Scale tags Operator proc
//================================================
unsigned int E3dOp_RotateTags(EOperator* LOperatorIn, int LOperation)
{
 E3dOperatorRotateTags*	LOperator=(E3dOperatorRotateTags*)LOperatorIn;
 E3dShapeSnapshot*	LShapeSnapshot=LOperator->ShapeSnapshots;
 E3dInterface**		LOutputs=NULL;
 unsigned int		LGmC, LShapeN=LOperator->NumOfShapeSnapshots, LOC, LNumOfOutputs=0, LNumOfOutputsAllocated=0;


 switch(LOperation)
 {
  case EOpDO:
   {
    E3dGeometry*		LGeometry;
    E3dGeometryCallbackStruct	LCBS;
    E3dMatrix			LMatrix;

    E3d_MatrixCopy(LOperator->Matrix, LMatrix);
    for(LGmC=0;LGmC<LShapeN;LGmC++, LShapeSnapshot++)
    {
     LGeometry=LShapeSnapshot->Geometry;
     switch(LGeometry->GeoType)
     {
      caseE3dMESH():
       {
	E3dSnapshotVertex*	LSnapshotVertex=((E3dMeshVerticesSnapshot*)LShapeSnapshot)->SnapshotVertices;
	E3dVertex*		LVertices=((E3dMesh*)LGeometry)->Vertices;
	E3dVertex*		LVertex;
	E3dCoordinate		mX, mY, mZ;
	unsigned int		LVC=((E3dMeshVerticesSnapshot*)LShapeSnapshot)->NumOfPoints;

	do
	{
	 LVertex=LVertices+LSnapshotVertex->Index;
	 mX=LSnapshotVertex->X;mY=LSnapshotVertex->Y;mZ=LSnapshotVertex->Z;
	 E3dM_MatrixTransform3x3(LMatrix, LVertex->X, LVertex->Y, LVertex->Z);
	 LSnapshotVertex++;
	} while(--LVC);

	E3d_GeometryUpdateForDisplay(LGeometry, E3dGF_SHAPE|E3dGF_NORMALS|E3dGF_REMAP_TEXTURES);
       }
      break;

      case E3dGEO_SPLINE:
       {
	E3dSpline*		LSpline=(E3dSpline*)LGeometry;

	_RotateSplineTags(LSpline, (E3dSplineCVsSnapshot*)LShapeSnapshot, LMatrix);

	E3d_SplineCreateLinearSegments(LSpline);
	E3d_SplineUpdateSegmentLengths(LSpline, LSpline->NumOfStepsForLength);

// Collect Output interfaces into a non-repeating array, so for example the "Skin" tool's
// InputChanged method will only get called once, even if many of its Splines change
//
	for(LOC=0;LOC<(LSpline->NumOfOutputs);LOC++) ELst_AddPointerAChk((void***)(&LOutputs), &LNumOfOutputs, &LNumOfOutputsAllocated, 4, LSpline->Outputs[LOC]);
       }
      break;

      case E3dGEO_FACE:
       {
	E3dFace*		LFace=(E3dFace*)LGeometry;
	E3dSplineCVsSnapshot*	LCVsSnapshot=((E3dFaceCVsSnapshot*)LShapeSnapshot)->SplineCVsSnapshots;
	unsigned int		LSC, LNumOfSplines=((E3dFaceCVsSnapshot*)LShapeSnapshot)->NumOfSplineCVsSnapshots;

	for(LSC=0;LSC<LNumOfSplines;LSC++, LCVsSnapshot++)
	{
	 _RotateSplineTags(LCVsSnapshot->Spline, LCVsSnapshot, LMatrix);
	 E3d_SplineCreateLinearSegments(LCVsSnapshot->Spline);
	 E3d_SplineUpdateSegmentLengths(LCVsSnapshot->Spline, LCVsSnapshot->Spline->NumOfStepsForLength);
	}

// Free these and E3d_DrawFace() or E3d_GetRenderTriangles() etc. will call E3d_FaceTesselate() when necessary
//
// This will speed things up when there are no Windows with Solid display modes
//
	if(LFace->Vertices) { EFree(LFace->Vertices);LFace->Vertices=NULL;LFace->NumOfVertices=0; }
	if(LFace->Triangles) { EFree(LFace->Triangles);LFace->Triangles=NULL;LFace->NumOfTriangles=0; }

// Collect Output interfaces into a non-repeating array, so for example the "Skin" tool's
// InputChanged method will only get called once, even if many of its Splines change
//
	for(LOC=0;LOC<(LFace->NumOfOutputs);LOC++) ELst_AddPointerAChk((void***)(&LOutputs), &LNumOfOutputs, &LNumOfOutputsAllocated, 4, LFace->Outputs[LOC]);
       }
      break;
     }
    }

    if(LOutputs)
    {
     LCBS.Reasons=E3dGF_SHAPE|E3dGF_NORMALS|E3dGF_REMAP_TEXTURES;
     E3d_CallOutputs(NULL, LOutputs, LNumOfOutputs, &LCBS);
     EFree(LOutputs);
    }
    return(E3dCHG_SHAPE);
   }

  case EOpUNDO:
   _M_UndoTagTransform();

  case EOpFREE:
   if(LShapeN)
   {
    E3dShapeSnapshot*	LShapeSnapshot=LOperator->ShapeSnapshots;

    for(LGmC=0;LGmC<LShapeN;LGmC++, LShapeSnapshot++)
    {
     E3d_ShapeSnapshotFree(LShapeSnapshot);
    }
    EFree(LOperator->ShapeSnapshots);LOperator->ShapeSnapshots=NULL;LOperator->NumOfShapeSnapshots=0;
   }
  break;
 }
 return(0);
}


//========================================
// Interactive rotating
//========================================
static EBool _Rotate(unsigned int LRotateAxes, int LPtrXDiff, int LPtrYDiff)
{
 E3dModel*	LModel;
 unsigned int	LC;
 EBool		LChanged=FALSE;


 if(LRotateAxes&E3dDO_X) E3dp_Rotation.X=-(float)LPtrYDiff*0.5;
 if(LRotateAxes&E3dDO_Y) E3dp_Rotation.Y=-(float)LPtrXDiff*0.5;
 if(LRotateAxes&E3dDO_Z) E3dp_Rotation.Z=(float)LPtrXDiff*0.5;

//Printf("LPtrYDiff %d E3dp_Rotation.X %f\n", LPtrYDiff, E3dp_Rotation.X);fflush(stdout);

 switch(E3dp_TransformTarget)
 {
  caseE3dpTT_OBJECT():
   {
    E3dOperatorRotateModels*	LOperator;
    E3dRotation*		LTransformBase;

// Store Model rotations for undo
//
    if(E3dp_PushOperatorsForUndo)
    {
     LOperator=(E3dOperatorRotateModels*)EOp_OperatorAllocate("Rotating Model(s)", sizeof(E3dOperatorRotateModels), E3dOp_RotateModels);

     if((LOperator->Models=(E3dModel**)EMalloc(sizeof(E3dModel*)*E3dp_NumOfTransformedModels))!=NULL)
     {
      LOperator->TransformAxes=LRotateAxes;
      memcpy(LOperator->Models, E3dp_TransformedModels, sizeof(E3dModel*)*E3dp_NumOfTransformedModels);
      LOperator->NumOfModels=E3dp_NumOfTransformedModels;

      LOperator->TransformBases=(E3dRotation*)EMalloc(sizeof(E3dRotation)*E3dp_NumOfTransformedModels);
      LTransformBase=LOperator->TransformBases;

      for(LC=0;LC<E3dp_NumOfTransformedModels;LC++, LTransformBase++)
      {
       LModel=E3dp_TransformedModels[LC];
       LTransformBase->X=LModel->Rotation.X;
       LTransformBase->Y=LModel->Rotation.Y;
       LTransformBase->Z=LModel->Rotation.Z;
       LModel->RefCnt+=1;
      }

      E3dp_AppendOps1Operator((EOperator*)LOperator);
     }
     E3d_CurrentOperator=(EOperator*)LOperator;

     E3dp_PushOperatorsForUndo=FALSE;
    }
    else LOperator=(E3dOperatorRotateModels*)E3d_CurrentOperator;

    LOperator->X=E3dp_Rotation.X;
    LOperator->Y=E3dp_Rotation.Y;
    LOperator->Z=E3dp_Rotation.Z;

    if(E3dOp_RotateModels((EOperator*)LOperator, EOpDO)) LChanged=TRUE;
   }
  break;

  case E3dpTT_TAG:
   {
    E3dOperatorRotateTags*	LOperator;
    E3dMatrix			LMatrix;

// Store Point (Vertex, Spline key etc.) positions for undo
//
    if(E3dp_PushOperatorsForUndo)
    {
     LOperator=(E3dOperatorRotateTags*)EOp_OperatorAllocate("Rotating tagged point(s)", sizeof(E3dOperatorRotateTags), E3dOp_RotateTags);

     LOperator->ShapeSnapshots=E3dp_ShapeSnapshots;
     LOperator->NumOfShapeSnapshots=E3dp_NumOfShapeSnapshots;
     E3dp_ShapeSnapshots=NULL;E3dp_NumOfShapeSnapshots=0;
     E3dp_AppendOps1Operator((EOperator*)LOperator);

     E3d_CurrentOperator=(EOperator*)LOperator;

     E3dp_PushOperatorsForUndo=FALSE;
    }
    else LOperator=(E3dOperatorRotateTags*)E3d_CurrentOperator;

    E3d_MatrixLoadIdentity(LMatrix);
    E3d_MatrixRotateXYZ(LMatrix, E3dp_Rotation.X, E3dp_Rotation.Y, E3dp_Rotation.Z, E3dZYX);
    E3d_MatrixCopy(LMatrix, LOperator->Matrix);

    E3dp_RefreshRotationScales(E3dp_Rotation.X, E3dp_Rotation.Y, E3dp_Rotation.Z, LRotateAxes);

    E3dOp_RotateTags((EOperator*)LOperator, EOpDO);
    E3dp_Redraw3DWindows(E3dDF_ALL, E3dVM_NORMAL3D);
   }
  break;
 }

 return(LChanged);
}



//================================================
// Translate Models Operator proc
//================================================
unsigned int E3dOp_TranslateModels(EOperator* LOperatorIn, int LOperation)
{
 E3dOperatorTranslateModels*	LOperator=(E3dOperatorTranslateModels*)LOperatorIn;
 E3dModel**			LModels=LOperator->Models;
 E3dModel*			LModel;
 unsigned int			LC, LN=LOperator->NumOfModels, LTransformAxes=LOperator->TransformAxes;
 unsigned int			LChanged=0;

 switch(LOperation)
 {
  case EOpDO:
   {
    E3dSceneModelsCallbackStruct	LCBS;
    E3dModel**				LTransformedModelRoots=NULL;
    E3d3DPosition*			LTransformBase=LOperator->TransformBases;
    E3dCoordinate			LTransX, LTransY, LTransZ;
    unsigned int			LNumOfTransformedModelRoots=0;
    EBool				LChg;


    for(LC=0;LC<LN;LC++, LTransformBase++)
    {
     LModel=LModels[LC];

     LChg=FALSE;
     if(LTransformAxes&E3dDO_X)
     {
      LTransX=LTransformBase->X+LOperator->X;if(LOperator->GridSnapToX) LTransX=E3d_SnapCoordinateTo(LTransX, LOperator->GridSnapToValX);
      if(LModel->Translation.X!=LTransX) { LModel->Translation.X=LTransX;LChg=TRUE;LChanged|=E3dCHG_TRANSFORM;if(LModel->Type==E3dMDL_LIGHT) LChanged|=E3dCHG_LIGHT; }
     }

     if(LTransformAxes&E3dDO_Y)
     {
      LTransY=LTransformBase->Y+LOperator->Y;if(LOperator->GridSnapToY) LTransY=E3d_SnapCoordinateTo(LTransY, LOperator->GridSnapToValY);
      if(LModel->Translation.Y!=LTransY) { LModel->Translation.Y=LTransY;LChg=TRUE;LChanged|=E3dCHG_TRANSFORM;if(LModel->Type==E3dMDL_LIGHT) LChanged|=E3dCHG_LIGHT; }
     }

     if(LTransformAxes&E3dDO_Z)
     {
      LTransZ=LTransformBase->Z+LOperator->Z;if(LOperator->GridSnapToZ) LTransZ=E3d_SnapCoordinateTo(LTransZ, LOperator->GridSnapToValZ);
      if(LModel->Translation.Z!=LTransZ) { LModel->Translation.Z=LTransZ;LChg=TRUE;LChanged|=E3dCHG_TRANSFORM;if(LModel->Type==E3dMDL_LIGHT) LChanged|=E3dCHG_LIGHT; }
     }

// Collect a non-repeating list of root Models of hierarchies that have selected Models
//
     if(LChg)
     {
      while(LModel->Parent) LModel=LModel->Parent;
      ELst_AddPointerChk((void***)(&LTransformedModelRoots), &LNumOfTransformedModelRoots, LModel);
     }
    }

    if(LChanged)
    {
     for(LC=0;LC<LNumOfTransformedModelRoots;LC++)
     {
      LModel=LTransformedModelRoots[LC];
      E3d_ModelHrcRefreshMatrices(LModel);
//     if(E3d_GLDataTransformed) E3d_ModelHrcRefreshForDisplay(LModel, FALSE);
     }
     if(LTransformedModelRoots) EFree(LTransformedModelRoots);

     LCBS.Reason=E3dCR_NODE_TRANSLATE;
     LCBS.Models=LModels;
     LCBS.NumOfModels=LN;
     for(LC=0;LC<LN;LC++, LTransformBase++)
     {
      LModel=LModels[LC];

      switch(LModel->Type)
      {
       case E3dMDL_LIGHT:
	if(LModel->Info)
	{
	 E3dLightInfo*	LLightInfo=(E3dLightInfo*)(LModel->Info[0]);
	 E3dLight*	LLight=LLightInfo->Light;

	 if(LLight)
	 {
	  LLight->Position.X=LModel->Translation.X;
	  LLight->Position.Y=LModel->Translation.Y;
	  LLight->Position.Z=LModel->Translation.Z;
	  LChanged|=E3dCHG_LIGHT;
	 }
	}
       break;
      }
     }

     E3d_CallCallbacks(E3d_Scene, E3d_Scene->Callbacks, E3d_Scene->NumOfCallbacks, &LCBS);

     LModel=LModels[0];
     E3dp_RefreshTranslationScales(LModel->Translation.X, LModel->Translation.Y, LModel->Translation.Z, LTransformAxes);

     return(LChanged);
    }
   }
  break;

  case EOpUNDO:
   {
    E3dSceneModelsCallbackStruct	LCBS;
    E3dModel**				LTransformedModelRoots=NULL;
    E3d3DPosition*			LTransformBase=LOperator->TransformBases;
    unsigned int			LNumOfTransformedModelRoots=0;
    EBool				LChg;


    for(LC=0;LC<LN;LC++, LTransformBase++)
    {
     LModel=LModels[LC];

     LChg=FALSE;
     if(LTransformAxes&E3dDO_X)
     {
      if(LModel->Translation.X!=LTransformBase->X) { LModel->Translation.X=LTransformBase->X;LChg=TRUE;LChanged|=E3dCHG_TRANSFORM;if(LModel->Type==E3dMDL_LIGHT) LChanged|=E3dCHG_LIGHT; }
     }

     if(LTransformAxes&E3dDO_Y)
     {
      if(LModel->Translation.Y!=LTransformBase->Y) { LModel->Translation.Y=LTransformBase->Y;LChg=TRUE;LChanged|=E3dCHG_TRANSFORM;if(LModel->Type==E3dMDL_LIGHT) LChanged|=E3dCHG_LIGHT; }
     }

     if(LTransformAxes&E3dDO_Z)
     {
      if(LModel->Translation.Z!=LTransformBase->Z) { LModel->Translation.Z=LTransformBase->Z;LChg=TRUE;LChanged|=E3dCHG_TRANSFORM;if(LModel->Type==E3dMDL_LIGHT) LChanged|=E3dCHG_LIGHT; }
     }

// Collect a non-repeating list of root Models of hierarchies that have selected Models
//
     if(LChg)
     {
      while(LModel->Parent) LModel=LModel->Parent;
      ELst_AddPointerChk((void***)(&LTransformedModelRoots), &LNumOfTransformedModelRoots, LModel);
     }
    }

    if(LChanged)
    {
     for(LC=0;LC<LNumOfTransformedModelRoots;LC++)
     {
      LModel=LTransformedModelRoots[LC];
      E3d_ModelHrcRefreshMatrices(LModel);
//     if(E3d_GLDataTransformed) E3d_ModelHrcRefreshForDisplay(LModel, FALSE);
     }
     if(LTransformedModelRoots) EFree(LTransformedModelRoots);

     LCBS.Reason=E3dCR_NODE_TRANSLATE;
     LCBS.Models=LModels;
     LCBS.NumOfModels=LN;
     for(LC=0;LC<LN;LC++, LTransformBase++)
     {
      LModel=LModels[LC];

      switch(LModel->Type)
      {
       case E3dMDL_LIGHT:
	if(LModel->Info)
	{
	 E3dLightInfo*	LLightInfo=(E3dLightInfo*)(LModel->Info[0]);
	 E3dLight*	LLight=LLightInfo->Light;

	 if(LLight)
	 {
	  LLight->Position.X=LModel->Translation.X;
	  LLight->Position.Y=LModel->Translation.Y;
	  LLight->Position.Z=LModel->Translation.Z;
	  LChanged|=E3dCHG_LIGHT;
	 }
	}
       break;
      }
     }

     E3d_CallCallbacks(E3d_Scene, E3d_Scene->Callbacks, E3d_Scene->NumOfCallbacks, &LCBS);

     LModel=LModels[0];
     E3dp_RefreshTranslationScales(LModel->Translation.X, LModel->Translation.Y, LModel->Translation.Z, LTransformAxes);

     return(LChanged);
    }
   }
  break;

  case EOpFREE:
   _M_ModelOperatorFree();
  break;
 }
 return(0);
}


static void _TranslateSplineTags(E3dSpline* LSpline, E3dSplineCVsSnapshot* LSplineCVsSnapshot, E3dCoordinate LGTransDiffX, E3dCoordinate LGTransDiffY, E3dCoordinate LGTransDiffZ)
{
 unsigned int		LVC=LSplineCVsSnapshot->NumOfCVs;

 switch(LSpline->SplineType)
 {
  case E3dSPL_BEZIER:
   {
    E3dSnapshotBezierCV*	LSnapshotBezierCV=(E3dSnapshotBezierCV*)(LSplineCVsSnapshot->SnapshotCVs);
    E3dBezierCV*		LBezierCVs=(E3dBezierCV*)(LSpline->CVs);
    E3dBezierCV*		LBezierCV;


    switch(LSplineCVsSnapshot->SubCV)
    {
     case E3dBEZ_PREVIOUS:
      do
      {
       LBezierCV=LBezierCVs+LSnapshotBezierCV->Index;

       E3d_MoveBezierCV(LBezierCV, LSplineCVsSnapshot->SubCV, LSnapshotBezierCV->Previous.X+LGTransDiffX, LSnapshotBezierCV->Previous.Y+LGTransDiffY, LSnapshotBezierCV->Previous.Z+LGTransDiffZ);
       LSnapshotBezierCV++;
      } while(--LVC);
     break;

     case E3dBEZ_POSITION:
      do
      {
       LBezierCV=LBezierCVs+LSnapshotBezierCV->Index;

       E3d_MoveBezierCV(LBezierCV, LSplineCVsSnapshot->SubCV, LSnapshotBezierCV->Position.X+LGTransDiffX, LSnapshotBezierCV->Position.Y+LGTransDiffY, LSnapshotBezierCV->Position.Z+LGTransDiffZ);
       LSnapshotBezierCV++;
      } while(--LVC);
     break;

     case E3dBEZ_NEXT:
      do
      {
       LBezierCV=LBezierCVs+LSnapshotBezierCV->Index;

       E3d_MoveBezierCV(LBezierCV, LSplineCVsSnapshot->SubCV, LSnapshotBezierCV->Next.X+LGTransDiffX, LSnapshotBezierCV->Next.Y+LGTransDiffY, LSnapshotBezierCV->Next.Z+LGTransDiffZ);
       LSnapshotBezierCV++;
      } while(--LVC);
     break;
    }
   }
  break;

  default:
   {
    E3dSnapshotSplineCV*	LSnapshotSplineCV=(E3dSnapshotSplineCV*)(LSplineCVsSnapshot->SnapshotCVs);
    E3dSplineCV*		LSplineCVs=(E3dSplineCV*)(LSpline->CVs);
    E3dSplineCV*		LSplineCV;

    do
    {
     LSplineCV=LSplineCVs+LSnapshotSplineCV->Index;
     LSplineCV->Position.X=LSnapshotSplineCV->Position.X+LGTransDiffX;
     LSplineCV->Position.Y=LSnapshotSplineCV->Position.Y+LGTransDiffY;
     LSplineCV->Position.Z=LSnapshotSplineCV->Position.Z+LGTransDiffZ;
     LSnapshotSplineCV++;
    } while(--LVC);
   }
  break;
 }
}


//================================================
// Translate tags Operator proc
//================================================
unsigned int E3dOp_TranslateTags(EOperator* LOperatorIn, int LOperation)
{
 E3dOperatorTranslateTags*	LOperator=(E3dOperatorTranslateTags*)LOperatorIn;
 E3dShapeSnapshot*		LShapeSnapshot=LOperator->ShapeSnapshots;
 E3dInterface**			LOutputs=NULL;
 unsigned int			LGmC, LShapeN=LOperator->NumOfShapeSnapshots, LOC, LNumOfOutputs=0, LNumOfOutputsAllocated=0;


 switch(LOperation)
 {
  case EOpDO:
   {
    E3dModel*			LModel;
    E3dGeometry*		LGeometry;
    E3dGeometryCallbackStruct	LCBS;
    E3dMatrix			LMatrix;
    E3dCoordinate		mX, mY, mZ, LGTransDiffX, LGTransDiffY, LGTransDiffZ;


    for(LGmC=0;LGmC<LShapeN;LGmC++, LShapeSnapshot++)
    {
     LGeometry=LShapeSnapshot->Geometry;

// Find the Model of this Geometry that has it selected to get the World->Local Matrix
// (by inverting LModel->LocalToWorldMatrix)
//
     LModel=E3d_GeometryGetSelectingModel(LGeometry);

     if(LModel) E3d_MatrixInvert3x3(LModel->LocalToWorldMatrix, LMatrix);
     else E3d_MatrixLoadIdentity(LMatrix);


     mX=LOperator->X;mY=LOperator->Y;mZ=LOperator->Z;
     E3dM_MatrixTransform3x3(LMatrix, LGTransDiffX, LGTransDiffY, LGTransDiffZ);

     switch(LGeometry->GeoType)
     {
      caseE3dMESH():
       {
	E3dSnapshotVertex*	LSnapshotVertex=((E3dMeshVerticesSnapshot*)LShapeSnapshot)->SnapshotVertices;
	E3dVertex*		LVertices=((E3dMesh*)LGeometry)->Vertices;
	E3dVertex*		LVertex;
	unsigned int		LVC=((E3dMeshVerticesSnapshot*)LShapeSnapshot)->NumOfPoints;

	do
	{
	 LVertex=LVertices+LSnapshotVertex->Index;

	 LVertex->X=LSnapshotVertex->X+LGTransDiffX;
	 LVertex->Y=LSnapshotVertex->Y+LGTransDiffY;
	 LVertex->Z=LSnapshotVertex->Z+LGTransDiffZ;
	 LSnapshotVertex++;
	} while(--LVC);


	E3d_GeometryUpdateForDisplay(LGeometry, E3dGF_SHAPE|E3dGF_REMAP_TEXTURES);
       }
      break;

      case E3dGEO_SPLINE:
       {
	E3dSpline*	LSpline=(E3dSpline*)LGeometry;


	_TranslateSplineTags(LSpline, (E3dSplineCVsSnapshot*)LShapeSnapshot, LGTransDiffX, LGTransDiffY, LGTransDiffZ);

	E3d_SplineCreateLinearSegments(LSpline);
	E3d_SplineUpdateSegmentLengths(LSpline, LSpline->NumOfStepsForLength);

// Collect Output interfaces into a non-repeating array, so for example the "Skin" tool's
// InputChanged method will only get called once, even if many of its Splines change
//
	for(LOC=0;LOC<(LSpline->NumOfOutputs);LOC++) ELst_AddPointerAChk((void***)(&LOutputs), &LNumOfOutputs, &LNumOfOutputsAllocated, 4, LSpline->Outputs[LOC]);
       }
      break;

      case E3dGEO_FACE:
       {
	E3dFace*		LFace=(E3dFace*)LGeometry;
	E3dSplineCVsSnapshot*	LCVsSnapshot=((E3dFaceCVsSnapshot*)LShapeSnapshot)->SplineCVsSnapshots;
	unsigned int		LSC, LNumOfSplines=((E3dFaceCVsSnapshot*)LShapeSnapshot)->NumOfSplineCVsSnapshots;


	for(LSC=0;LSC<LNumOfSplines;LSC++, LCVsSnapshot++)
	{
	 _TranslateSplineTags(LCVsSnapshot->Spline, LCVsSnapshot, LGTransDiffX, LGTransDiffY, LGTransDiffZ);
	 E3d_SplineCreateLinearSegments(LCVsSnapshot->Spline);
	 E3d_SplineUpdateSegmentLengths(LCVsSnapshot->Spline, LCVsSnapshot->Spline->NumOfStepsForLength);
	}

// Free these and E3d_DrawFace() or E3d_GetRenderTriangles() etc. will call E3d_FaceTesselate() when necessary
//
// This will speed things up when there are no Windows with Solid display modes
//
	if(LFace->Vertices) { EFree(LFace->Vertices);LFace->Vertices=NULL;LFace->NumOfVertices=0; }
	if(LFace->Triangles) { EFree(LFace->Triangles);LFace->Triangles=NULL;LFace->NumOfTriangles=0; }

// Collect Output interfaces into a non-repeating array, so for example the "Skin" tool's
// InputChanged method will only get called once, even if many of its Splines change
//
	for(LOC=0;LOC<(LFace->NumOfOutputs);LOC++) ELst_AddPointerAChk((void***)(&LOutputs), &LNumOfOutputs, &LNumOfOutputsAllocated, 4, LFace->Outputs[LOC]);
       }
      break;
     }
    }

    if(LOutputs)
    {
     LCBS.Reasons=E3dGF_SHAPE|E3dGF_NORMALS|E3dGF_REMAP_TEXTURES;
     E3d_CallOutputs(NULL, LOutputs, LNumOfOutputs, &LCBS);
     EFree(LOutputs);
    }
    return(E3dCHG_SHAPE);
   }

  case EOpUNDO:
   _M_UndoTagTransform();

  case EOpFREE:
   if(LShapeN)
   {
    E3dShapeSnapshot*	LShapeSnapshot=LOperator->ShapeSnapshots;

    for(LGmC=0;LGmC<LShapeN;LGmC++, LShapeSnapshot++)
    {
     E3d_ShapeSnapshotFree(LShapeSnapshot);
    }
    EFree(LOperator->ShapeSnapshots);LOperator->ShapeSnapshots=NULL;LOperator->NumOfShapeSnapshots=0;
   }
  break;
 }
 return(0);
}


//========================================
// Interactive translating
//========================================
static EBool _Translate(E3dWindow* L3DWindow, unsigned int LTranslateAxes, int LPtrXDiff, int LPtrYDiff)
{
 E3dWindowSettings*	L3DWindowSettings=&(L3DWindow->Settings);
 E3dModel*		LModel;
 E3dCoordinate		LTransX, LTransY;
 E3d3DPosition		LDelta;
 unsigned int		LC;
 EBool			LChanged=FALSE;



 switch(L3DWindow->ViewMode)
 {
  case E3dVM_PERSPECTIVE:
  case E3dVM_TOP:
  case E3dVM_FRONT:
  case E3dVM_RIGHT:
   E3dp_WindowComputeTranslation(L3DWindow, (E3dCoordinate)LPtrXDiff, -(E3dCoordinate)LPtrYDiff, LTranslateAxes, &LDelta);
  break;

  case E3dVM_SCHEMATICS:
   {
    E3dModel**		LTransformedModelRoots=NULL;
    unsigned int	LNumOfTransformedModelRoots=0;


    E3dp_WindowComputeTranslation(L3DWindow, (E3dCoordinate)LPtrXDiff, -(E3dCoordinate)LPtrYDiff, LTranslateAxes, &LDelta);
    switch(E3dp_TransformTarget)
    {
     caseE3dpTT_OBJECT():
      for(LC=0;LC<E3dp_NumOfTransformedModels;LC++)
      {
       LModel=E3dp_TransformedModels[LC];

       LTransX=LModel->TransformBase.X+LDelta.X;
       LTransY=LModel->TransformBase.Y+LDelta.Y;

       if(L3DWindowSettings->GridSnapToX) LTransX=E3d_SnapCoordinateTo(LTransX, L3DWindowSettings->GridSize.X);
       if(L3DWindowSettings->GridSnapToY) LTransY=E3d_SnapCoordinateTo(LTransY, L3DWindowSettings->GridSize.Y);

       if((LModel->SchemTranslation.X!=LTransX)||(LModel->SchemTranslation.Y!=LTransY))
       {
	LModel->SchemTranslation.X=LTransX;
	LModel->SchemTranslation.Y=LTransY;

// Collect a non-repeating list of affected hierarchies
//
	while(LModel->Parent) LModel=LModel->Parent;
	ELst_AddPointerChk((void***)(&LTransformedModelRoots), &LNumOfTransformedModelRoots, LModel);

	LChanged=TRUE;
       }
      }
      if(LChanged)
      {
       if(LTransformedModelRoots)
       {
	for(LC=0;LC<LNumOfTransformedModelRoots;LC++) E3d_ModelHrcRefreshSchemPositions(LTransformedModelRoots[LC]);
	EFree(LTransformedModelRoots);
       }
       E3dp_Redraw3DWindows(E3dDF_ALL, E3dVM_SCHEMATICS);
      }
     break;
    }
   }
  return(FALSE);
 }


//printf("Delta %f,%f,%f %08x\n", LDelta.X, LDelta.Y, LDelta.Z, LTranslateAxes);fflush(stdout);

 switch(E3dp_TransformTarget)
 {
  caseE3dpTT_OBJECT():
   {
    E3dOperatorTranslateModels*	LOperator;
    E3d3DPosition*		LTransformBase;

// Store Model positions for undo
//
    if(E3dp_PushOperatorsForUndo)
    {
     LOperator=(E3dOperatorTranslateModels*)EOp_OperatorAllocate("Translating Model(s)", sizeof(E3dOperatorTranslateModels), E3dOp_TranslateModels);

     if((LOperator->Models=(E3dModel**)EMalloc(sizeof(E3dModel*)*E3dp_NumOfTransformedModels))!=NULL)
     {
      LOperator->TransformAxes=LTranslateAxes;
      memcpy(LOperator->Models, E3dp_TransformedModels, sizeof(E3dModel*)*E3dp_NumOfTransformedModels);
      LOperator->NumOfModels=E3dp_NumOfTransformedModels;

      LOperator->TransformBases=(E3d3DPosition*)EMalloc(sizeof(E3d3DPosition)*E3dp_NumOfTransformedModels);
      LTransformBase=LOperator->TransformBases;

      for(LC=0;LC<E3dp_NumOfTransformedModels;LC++, LTransformBase++)
      {
       LModel=E3dp_TransformedModels[LC];
       LTransformBase->X=LModel->Translation.X;
       LTransformBase->Y=LModel->Translation.Y;
       LTransformBase->Z=LModel->Translation.Z;
       LModel->RefCnt+=1;
      }


      E3dp_AppendOps1Operator((EOperator*)LOperator);
     }
     E3d_CurrentOperator=(EOperator*)LOperator;

     E3dp_PushOperatorsForUndo=FALSE;
    }
    else LOperator=(E3dOperatorTranslateModels*)E3d_CurrentOperator;

    LOperator->X=LDelta.X;
    LOperator->Y=LDelta.Y;
    LOperator->Z=LDelta.Z;
    LOperator->GridSnapToX=L3DWindowSettings->GridSnapToX;
    LOperator->GridSnapToY=L3DWindowSettings->GridSnapToY;
    LOperator->GridSnapToZ=L3DWindowSettings->GridSnapToZ;
    LOperator->GridSnapToValX=L3DWindowSettings->GridSize.X;
    LOperator->GridSnapToValY=L3DWindowSettings->GridSize.Y;
    LOperator->GridSnapToValZ=L3DWindowSettings->GridSize.Z;

    if(E3dOp_TranslateModels((EOperator*)LOperator, EOpDO)) LChanged=TRUE;
   }
  break;

  case E3dpTT_TAG:
   {
    E3dOperatorTranslateTags*	LOperator;

// Store Point (Vertex, Spline key etc.) positions for undo
//
    if(E3dp_PushOperatorsForUndo)
    {
     LOperator=(E3dOperatorTranslateTags*)EOp_OperatorAllocate("Translating tagged point(s)", sizeof(E3dOperatorTranslateTags), E3dOp_TranslateTags);

     LOperator->TransformAxes=LTranslateAxes;
     LOperator->ShapeSnapshots=E3dp_ShapeSnapshots;
     LOperator->NumOfShapeSnapshots=E3dp_NumOfShapeSnapshots;
     E3dp_ShapeSnapshots=NULL;E3dp_NumOfShapeSnapshots=0;
     E3dp_AppendOps1Operator((EOperator*)LOperator);

     E3d_CurrentOperator=(EOperator*)LOperator;

     E3dp_PushOperatorsForUndo=FALSE;
    }
    else LOperator=(E3dOperatorTranslateTags*)E3d_CurrentOperator;

    if(LTranslateAxes&E3dDO_X)
    {
     if(L3DWindowSettings->GridSnapToX) LDelta.X=E3d_SnapCoordinateTo(LDelta.X, L3DWindowSettings->GridSize.X);
    }
    if(LTranslateAxes&E3dDO_Y)
    {
     if(L3DWindowSettings->GridSnapToY) LDelta.Y=E3d_SnapCoordinateTo(LDelta.Y, L3DWindowSettings->GridSize.Y);
    }
    if(LTranslateAxes&E3dDO_Z)
    {
     if(L3DWindowSettings->GridSnapToZ) LDelta.Z=E3d_SnapCoordinateTo(LDelta.Z, L3DWindowSettings->GridSize.Z);
    }

    LOperator->X=LDelta.X;
    LOperator->Y=LDelta.Y;
    LOperator->Z=LDelta.Z;
    E3dp_RefreshTranslationScales(LDelta.X, LDelta.Y, LDelta.Z, LTranslateAxes);

    E3dOp_TranslateTags((EOperator*)LOperator, EOpDO);
    E3dp_Redraw3DWindows(E3dDF_ALL, E3dVM_NORMAL3D);
   }
  break;
 }
 return(LChanged);
}


//========================================
// Interactive transformation handler
//========================================
void E3dp_InteractiveTransform(E3dWindow* L3DWindow, int LPtrX, int LPtrY, unsigned int LButtonState)
{
 int	LPtrXDiff, LPtrYDiff;
 int	LScaleAxes=E3dp_ScaleFlags,
	LRotateAxes=E3dp_RotateFlags,
	LTranslateAxes=E3dp_GetTranslateAxes(L3DWindow, E3dp_TranslateFlags, LButtonState);


 LPtrXDiff=LPtrX-E3dp_BasePtrX;
 LPtrYDiff=LPtrY-E3dp_BasePtrY;

 if((LPtrXDiff!=0)||(LPtrYDiff!=0))
 {
  if(E3dp_NumOfTransformedModels>0)
  {
   E3dScene*	LScene=E3d_Scene;
   unsigned int	LChanged=0;


// Translate
//
   if(LTranslateAxes)
   {
    LChanged|=_Translate(L3DWindow, LTranslateAxes, LPtrXDiff, LPtrYDiff);

    if(LChanged&E3dCHG_LIGHT) E3d_SceneLightsRefreshGL(LScene);
   }
   else
   {
// Rotate
//
    if((LButtonState&Button1Mask)==0) LRotateAxes&=(E3dDO_ALL-E3dDO_X);
    if((LButtonState&Button2Mask)==0) LRotateAxes&=(E3dDO_ALL-E3dDO_Y);
    if((LButtonState&Button3Mask)==0) LRotateAxes&=(E3dDO_ALL-E3dDO_Z);

    if(LRotateAxes)
    {
     LChanged|=_Rotate(LRotateAxes, LPtrXDiff, LPtrYDiff);
    }
    else
    {
// Scale
//
     if((LButtonState&Button1Mask)==0) LScaleAxes&=(E3dDO_ALL-E3dDO_X);
     if((LButtonState&Button2Mask)==0) LScaleAxes&=(E3dDO_ALL-E3dDO_Y);
     if((LButtonState&Button3Mask)==0) LScaleAxes&=(E3dDO_ALL-E3dDO_Z);
     LChanged|=_Scale(LScaleAxes, LPtrXDiff, LPtrYDiff);
    }
   }

   if(LChanged)
   {
    if(_ShadowMap) _ShadowMap->ShadowMapValid=FALSE;
    E3dp_Redraw3DWindows(E3dDF_ALL, E3dVM_NORMAL3D);
   }
  }
 }
}


//========================================
// Initialize Model scaling
//========================================
static void _InitObjectScaling(E3dWindow* L3DWindow)
{
 switch(L3DWindow->ViewMode)
 {
  case E3dVM_SCHEMATICS:
  break;

  default:
   if(E3dp_NumOfTransformedModels) E3dp_PushOperatorsForUndo=TRUE;
  break;
 }
}


//========================================
// Initialize Model rotating
//========================================
static void _InitObjectRotating(E3dWindow* L3DWindow)
{
 switch(L3DWindow->ViewMode)
 {
  case E3dVM_SCHEMATICS:
  break;

  default:
   if(E3dp_NumOfTransformedModels) E3dp_PushOperatorsForUndo=TRUE;
  break;
 }
}


//========================================
// Initialize Model translating
//========================================
static void _InitObjectTranslating(E3dWindow* L3DWindow)
{
 E3dModel*	LModel;
 int		LC;

 switch(L3DWindow->ViewMode)
 {
  case E3dVM_SCHEMATICS:
   for(LC=0;LC<E3dp_NumOfTransformedModels;LC++)
   {
    LModel=E3dp_TransformedModels[LC];
    LModel->TransformBase.X=LModel->SchemTranslation.X;
    LModel->TransformBase.Y=LModel->SchemTranslation.Y;
    LModel->TransformBase.Z=LModel->SchemTranslation.Z;
   }
  break;

  default:
   if(E3dp_NumOfTransformedModels) E3dp_PushOperatorsForUndo=TRUE;
  break;
 }
}


//========================================================================
// Button pressed on a 3D window, set up for interactive transforming
//========================================================================
void E3dp_InitInteractiveTransform(E3dWindow* L3DWindow)
{
 char*		LStrP;


 if((E3dp_ScaleFlags==0)&&(E3dp_RotateFlags==0)&&(E3dp_TranslateFlags==0)) return;

 E3dp_NumOfTransformedModels=E3d_SceneGetSelectedModels(E3d_Scene, &E3dp_TransformedModels, TRUE);

 if(E3dp_NumOfTransformedModels>0)
 {
  E3dModel*	LModel;
  unsigned int	LC;


// Collect a non-repeating list of root Models of hierarchies that have selected Models
//
  for(LC=0;LC<E3dp_NumOfTransformedModels;LC++)
  {
   LModel=E3dp_TransformedModels[LC];

   while(LModel->Parent) LModel=LModel->Parent;
   ELst_AddPointerChk((void***)(&E3dp_TransformedModelRoots), &E3dp_NumOfTransformedModelRoots, LModel);
  }


  if(E3dp_TranslateFlags)
  {
   E3dp_Translation.X=0.0;
   E3dp_Translation.Y=0.0;
   E3dp_Translation.Z=0.0;

// Store translating "base" value (we need it to snap to grids)
//
   switch(E3dp_TransformTarget)
   {
    caseE3dpTT_OBJECT():
     LStrP="OBJECT";

     _InitObjectTranslating(L3DWindow);
    break;

    case E3dpTT_TAG:
     LStrP="TAG";

     switch(L3DWindow->ViewMode)
     {
      case E3dVM_SCHEMATICS:
      break;

      default:
       E3dp_ShapeSnapshots=E3d_SceneTaggedPointsToTransformPoints(E3d_Scene, &E3dp_NumOfShapeSnapshots);

       if(E3dp_NumOfShapeSnapshots>0)
       {
// This flag indicates that we should push E3dp_ShapeSnapshots on the undo-stack before we start changing them
//
	E3dp_PushOperatorsForUndo=TRUE;
       }
       else
       {
	XeToggleButtonSetState(_TTB_ObjectW, True, True);
	E3dp_TransformTarget=E3dpTT_OBJECT_TMP_FROM_TAG;
	LStrP="OBJECT";

	_InitObjectTranslating(L3DWindow);

        E3dp_PrintMessage(0, 7000, "There are no tagged points, translating Object(s).");
        return;
       }
      break;
     }
    break;

    case E3dpTT_CENTER:		LStrP="CENTER";break;
    case E3dpTT_TEXTURE:	LStrP="TEXTURE";break;
    default:			LStrP="";break;
   }


   switch(E3dp_TransformMode)
   {
    case E3dpTM_LOCAL:
    case E3dpTM_GLOBAL:
     E3dp_PrintMessage(0, 0, "Translate %s   L: along X   M: along Y   R: along Z", LStrP);
    break;

    case E3dpTM_VIEW:
     E3dp_PrintMessage(0, 0, "Translate %s   L: Along view", LStrP);
    break;
   }
  }




  if(E3dp_RotateFlags)
  {
   E3dp_Rotation.X=0.0;
   E3dp_Rotation.Y=0.0;
   E3dp_Rotation.Z=0.0;

// Store rotating "base" values
//
   switch(E3dp_TransformTarget)
   {
    caseE3dpTT_OBJECT():
     LStrP="OBJECT";

     _InitObjectRotating(L3DWindow);
    break;

    case E3dpTT_TAG:
     LStrP="TAG";

     switch(L3DWindow->ViewMode)
     {
      case E3dVM_SCHEMATICS:
      break;

      default:
       E3dp_ShapeSnapshots=E3d_SceneTaggedPointsToTransformPoints(E3d_Scene, &E3dp_NumOfShapeSnapshots);

       if(E3dp_NumOfShapeSnapshots>0)
       {
// This flag indicates that we should push E3dp_ShapeSnapshots on the undo-stack before we start changing them
//
	E3dp_PushOperatorsForUndo=TRUE;
       }
       else
       {
	XeToggleButtonSetState(_TTB_ObjectW, True, True);
	E3dp_TransformTarget=E3dpTT_OBJECT_TMP_FROM_TAG;
	LStrP="OBJECT";

	_InitObjectRotating(L3DWindow);

        E3dp_PrintMessage(0, 7000, "There are no tagged points, rotating Object(s).");
        return;
       }
      break;
     }
    break;

    case E3dpTT_CENTER:		LStrP="CENTER";break;
    case E3dpTT_TEXTURE:	LStrP="TEXTURE";break;
    default:			LStrP="";break;
   }


   switch(E3dp_TransformMode)
   {
    case E3dpTM_LOCAL:
    case E3dpTM_GLOBAL:
     E3dp_PrintMessage(0, 0, "Rotate %s   L: along X   M: along Y   R: along Z", LStrP);
    break;

    case E3dpTM_VIEW:
     E3dp_PrintMessage(0, 0, "Rotate %s   L: Along view", LStrP);
    break;
   }
  }





  if(E3dp_ScaleFlags)
  {
   E3dp_Scaling.X=1.0;
   E3dp_Scaling.Y=1.0;
   E3dp_Scaling.Z=1.0;

// Reset scaling "base" value
//
   switch(E3dp_TransformTarget)
   {
    caseE3dpTT_OBJECT():
     LStrP="OBJECT";
     _InitObjectScaling(L3DWindow);
    break;

    case E3dpTT_TAG:
     LStrP="TAG";
     switch(L3DWindow->ViewMode)
     {
      case E3dVM_SCHEMATICS:
      break;

      default:
       E3dp_ShapeSnapshots=E3d_SceneTaggedPointsToTransformPoints(E3d_Scene, &E3dp_NumOfShapeSnapshots);

       if(E3dp_NumOfShapeSnapshots>0)
       {
	E3dp_PushOperatorsForUndo=TRUE;	// This flag indicates that we should push E3dp_ShapeSnapshots on the undo-stack before we start changing them
       }
       else
       {
	XeToggleButtonSetState(_TTB_ObjectW, True, True);
	E3dp_TransformTarget=E3dpTT_OBJECT_TMP_FROM_TAG;
	LStrP="OBJECT";

	_InitObjectScaling(L3DWindow);

        E3dp_PrintMessage(0, 7000, "SclThere are no tagged points, scaling Object(s).");
        return;
       }
      break;
     }
    break;

    case E3dpTT_CENTER:		LStrP="CENTER";break;
    case E3dpTT_TEXTURE:	LStrP="TEXTURE";break;
    default:			LStrP="";break;
   }
  }
 } else E3dp_PrintMessage(0, 2500, "Nothing is selected");
}
