//================================================================
// EQUINOX-3D plugin
//
// - Reset Transforms
// - Freeze Transforms
// - Freeze Scaling
//
// AUTHOR:  Gabor Nagy
// DATE:    2026-Jan-07 02:45:00
//
// Copyright (C) 1996-2026 by Gabor Nagy. All rights reserved.
//================================================================
#include <stdio.h>

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

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

#include <Xe/Dialog.h>

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

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


char*  E_PluginInfo = " - Reset Transforms\n - Freeze Transforms\n - Freeze Scaling\n - Origin to center";

static XeDWidget  _MenuButtons[16];static int  _MenuButtons_Count = 0;
static XeDWidget  _ToolBarButtons[16];static int  _ToolBarButtons_Count = 0;

static EPlugin*         _Plugin = NULL;

static EOperatorClass*  _OperatorClass = NULL;


//========================================
// Freeze transforms on selection
//========================================
static void _CB_FreezeToWorld(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dScene*     LScene = E3d_Scene;
	EChangeFlags  LChanged = 0;

	EIndex  LRI = 0;const EIndex  LRN = LScene->RootNodes.Count;E3dNode**  LRootNodes = LScene->RootNodes.Items;for(;LRI < LRN;LRI++)
	{
		E3dNode*  LRootNode = LRootNodes[LRI];
		E3dNode*  LNode = LRootNode;
		EBool     LHrcChanged = FALSE;

		do
		{
			if(LNode->Selection != E3dNodeSelNONE)
			{
				E3dModel*  LModel = E_DynamicCast(LNode, E3dModel);if(LModel)
				{
					E3dMatrix  LMatrix;E3dMatrix_Copy(LNode->LocalToWorldMatrix, LMatrix);

					EIndex  LObjI = 0;const EIndex  LObjN = LModel->Objects.Count;E3dObject**  LObjects = LModel->Objects.Items;for(;LObjI < LObjN;LObjI++)
					{
						E3dObject*  LObject = LObjects[LObjI];

						if(EObject_IsSubClass(LObject, E3dGeometry))
						{
							E3dGeometry*  LGeometry = (E3dGeometry*)LObject;

							E3dGeometry_Transform(LGeometry, LMatrix);
							LHrcChanged = TRUE;
							LChanged |= E3dObject_GetChangeFlagsByClass(LObject)|E3dChgNodeTRANSFORM;
						}
					}

					E3dModel_ResetTransforms((E3dModel*)LNode);
				}
			}

			LNode = LNode->Next;
		} while(LNode);

		if(LHrcChanged)
		{
			E3dNodeHrc_UpdateForDisplay(LRootNode, E3duf_SHAPE);
		}
	}

	E3dWindows_RedrawByChangeFlags(LScene, LChanged);
	E3dTransformPanel_Update(NULL,  E3dDoALL, E3dDoALL, E3dDoALL,  E3dpDO_NOT_CHANGE,  0);
}


//========================================
// Freeze transforms on selections
// - Branch freeze to parent of branch
//========================================
static void _CB_FreezeBranchToItsParent(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dScene*     LScene = E3d_Scene;
	EIndex        LBranchesFroze = 0;
	EChangeFlags  LChanged = 0;

	EIndex  LRI = 0;const EIndex  LRN = LScene->RootNodes.Count;E3dNode**  LRootNodes = LScene->RootNodes.Items;for(;LRI < LRN;LRI++)
	{
		E3dNode*      LRootNode;
		EChangeFlags  LChgF = 0;
		EBool         LHrcChanged = FALSE;
		E3dNode*      LNode = LRootNode = LRootNodes[LRI];for(;LNode;)
		{
// Take care of this branch in one sitting
//
			if(LNode->Selection == E3dNodeSelBRANCH_ROOT)
			{
				E3dNode*   LBranchRootNode = LNode;
				E3dNode*   LNodeNextAfterBranch = E3dNode_BranchGetNodeAfter(LBranchRootNode);
				E3dMatrix  LBranchParentWorldToLocalMatrix;

				if(LRootNode->Parent)
				{
					E3dMatrix_Invert3x4(LRootNode->Parent->LocalToWorldMatrix, LBranchParentWorldToLocalMatrix);
				}
				else E3dMatrix_LoadIdentity(LBranchParentWorldToLocalMatrix);


//printf("%s() %s:%d [%s] [%s]\n", __FUNCTION__, __FILE__, __LINE__, LNode->Name, LNodeNextAfterBranch ? LNodeNextAfterBranch->Name : "-");fflush(stdout);

// E3dNodeHrc_UpdateConnections can handle branches (the branch root doesn't have to be a hierarchy root).
// We just have to call it on the hierarchy root once we're done.
//
				E3dNodeHrc_UpdateConnections(LBranchRootNode);
				E3dNodeHrc_UpdateMatrices(LBranchRootNode, FALSE);

				for(;LNode;LNode = LNode->Next)
				{
					E3dModel*  LModel = E_DynamicCast(LNode, E3dModel);

//					E3dMatrix  LMatrix;E3dMatrix_Mult4x4_3(LBranchParentWorldToLocalMatrix, LNode->LocalToWorldMatrix, LMatrix);
					E3dMatrix  LMatrix;E3dMatrix_Copy(LNode->LocalToWorldMatrix, LMatrix);

					if(LModel)
					{
						EIndex  LObjI = 0;const EIndex  LObjN = LModel->Objects.Count;E3dObject**  LObjects = LModel->Objects.Items;for(;LObjI < LObjN;LObjI++)
						{
							E3dObject*  LObject = LObjects[LObjI];

							if(EObject_IsSubClass(LObject, E3dGeometry))
							{
								E3dGeometry*  LGeometry = (E3dGeometry*)LObject;

								E3dGeometry_Transform(LGeometry, LMatrix);
								LHrcChanged = TRUE;
								LChgF |= E3dObject_GetChangeFlagsByClass(LObject)|E3dChgNodeTRANSFORM;
								LChanged |= LChgF;
							}
						}

						E3dModel_ResetTransforms(LModel);
					}
				}
				LNode = LNodeNextAfterBranch;

// We must restore connections in the hierarchy, so if there are other selected branches, they will work right
//
//printf("%s() %s:%d %s{%s\n", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, ECodeNormal);fflush(stdout);
				E3dNodeHrc_UpdateConnections(LRootNode);

				LBranchesFroze++;
			}
			else LNode = LNode->Next;
		}

		if(LHrcChanged)
		{
			E3dNodeHrc_UpdateMatrices(LRootNode, TRUE);
			E3dNodeHrc_CallNodeInfoUpdateProcs(LRootNode, LChgF, FALSE);
			E3dNodeHrc_UpdateForDisplay(LRootNode, E3duf_SHAPE);
		}
	}

	if(LBranchesFroze == 0) E3dp_PrintMessage(ELogWARNING, 5000, "No branches selected.");
	else
	{
		if(LBranchesFroze == 1) E3dp_PrintMessage(ELogMESSAGE, 5000, "Froze: 1 branch.");
		else E3dp_PrintMessage(ELogMESSAGE, 5000, "Froze: %d branches", LBranchesFroze);
	}

	E3dWindows_RedrawByChangeFlags(LScene, LChanged);
	E3dTransformPanel_Update(NULL,  E3dDoALL, E3dDoALL, E3dDoALL,  E3dpDO_NOT_CHANGE,  0);
}


//========================================
// Freeze transforms on selections
// - Branch freeze to root of branch
//========================================
static void _CB_FreezeBranchToItsRoot(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dScene*     LScene = E3d_Scene;
	EChangeFlags  LChanged = 0;

	EIndex  LBranchesFroze = 0;
	EIndex  LRI = 0;const EIndex  LRN = LScene->RootNodes.Count;E3dNode**  LRootNodes = LScene->RootNodes.Items;for(;LRI < LRN;LRI++)
	{
		E3dNode*  LRootNode = LRootNodes[LRI];
		E3dNode*  LNode = LRootNode;
		EChangeFlags	LChgF = 0;

		do
		{
//printf("In\n");fflush(stdout);
// Take care of this branch in one sitting
//
			if(LNode->Selection == E3dNodeSelBRANCH_ROOT)
			{
				if(LNode->Child)
				{
					E3dNode*  LBranchRootNode = LNode;
					E3dNode*  LNodeNextAfterBranch;

					if((LNodeNextAfterBranch = E3dNode_BranchGetLastNode(LBranchRootNode)) != NULL) LNodeNextAfterBranch = LNodeNextAfterBranch->Next;
					else LNodeNextAfterBranch = NULL;

					E3dTransformSet  LTransformSetBackup;

					E3dModel*  LBranchRootModel = E_DynamicCast(LBranchRootNode, E3dModel);if(LBranchRootModel)
					{
						memcpy(&LTransformSetBackup, &LBranchRootModel->TransformSet, sizeof(E3dTransformSet));  // Back up transforms of branch root

						EList_Init(LBranchRootModel->TransformSet.Transforms);

						E3dTransformSet_Reset(&LBranchRootModel->TransformSet, FALSE);
					}

					E3dNodeHrc_UpdateMatrices(LBranchRootNode, FALSE);


					LNode = LNode->Child;
					do
					{
						E3dMatrix  LMatrix;E3dMatrix_Copy(LNode->LocalToWorldMatrix, LMatrix);

						E3dModel*  LModel = E_DynamicCast(LNode, E3dModel);if(LModel)
						{
							EIndex  LObjI = 0;const EIndex  LObjN = LModel->Objects.Count;E3dObject**  LObjects = LModel->Objects.Items;for(;LObjI < LObjN;LObjI++)
							{
								E3dObject*  LObject = LObjects[LObjI];

								if(EObject_IsSubClass(LObject, E3dGeometry))
								{
									E3dGeometry*  LGeometry = (E3dGeometry*)LObject;

									E3dGeometry_Transform(LGeometry, LMatrix);
									E3dObject_UpdateForDisplay(LObject,  E3duf_SHAPE, 0,  LScene);
									LChgF |= E3dObject_GetChangeFlagsByClass(LObject)|E3dChgNodeTRANSFORM;
									LChanged |= LChgF;
								}
							}

							E3dModel_ResetTransforms(LModel);
						}

						if((LNode = LNode->Next) == NULL) break;
					} while(LNode != LNodeNextAfterBranch);
					LBranchesFroze++;

					if(LBranchRootModel) { memcpy(&LBranchRootModel->TransformSet, &LTransformSetBackup, sizeof(E3dTransformSet)); } // Back up transforms of branch root

					LNode = LNodeNextAfterBranch;
				}
				else LNode = LNode->Next;
			}
			else LNode = LNode->Next;
		} while(LNode);

		if(LChgF)
		{
			E3dNodeHrc_UpdateConnections(LRootNode);
			E3dNodeHrc_UpdateMatrices(LRootNode, TRUE);
			E3dNodeHrc_CallNodeInfoUpdateProcs(LRootNode, LChgF, FALSE);
			E3dNodeHrc_UpdateForDisplay(LRootNode, LChgF);
		}
	}

	if(LBranchesFroze == 0) E3dp_PrintMessage(ELogWARNING, 5000, "No branches selected.");
	else
	{
		if(LBranchesFroze == 1) E3dp_PrintMessage(ELogMESSAGE, 5000, "Froze: 1 branch");
		else E3dp_PrintMessage(ELogMESSAGE, 5000, "Froze: %d branches", LBranchesFroze);
	}

	E3dWindows_RedrawByChangeFlags(LScene, LChanged);
	E3dTransformPanel_Update(NULL,  E3dDoALL, E3dDoALL, E3dDoALL,  E3dpDO_NOT_CHANGE,  E3dTrPanelALL);
}


//========================================
// Freeze rotation on selection
//========================================
static void _CB_FreezeRotation(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dScene*     LScene = E3d_Scene;
	EIndex        LTotalNumOfGeometriesChanged = 0;
	EChangeFlags  LChanged = 0;

	EIndex  LRI = 0;const EIndex  LRN = LScene->RootNodes.Count;E3dNode**  LRootNodes = LScene->RootNodes.Items;for(;LRI < LRN;LRI++)
	{
		E3dNode*  LRootNode = LRootNodes[LRI];
		EIndex    LHrcNumOfGeometriesChanged = 0;

		E3dNode*  LNode = LRootNode;for(;LNode;)
		{
			if(LNode->Selection == E3dNodeSelBRANCH_ROOT)
			{
				LHrcNumOfGeometriesChanged += E3dNodeHrc_FreezeRotation(LNode);

				LChanged |= E3dNodeHrc_GetTransformChangeFlagsByObjects(LNode) | E3dChgNodeTRANSFORM;

				LNode = E3dNode_BranchGetNodeAfter(LNode);
			}
			else LNode = LNode->Next;
		}

	 LTotalNumOfGeometriesChanged += LHrcNumOfGeometriesChanged;
	 if(LHrcNumOfGeometriesChanged) E3dNodeHrc_UpdateForDisplay(LRootNode, E3duf_SHAPE);

/*
	 if(LRootNode->Selection == E3dNodeSelBRANCH_ROOT)
	 {
		 LNumGeometriesChanged += E3dNodeHrc_FreezeRotation(LRootNode);

		 LChanged |= E3dNodeHrc_GetTransformChangeFlagsByObjects(LRootNode) | E3dChgNodeTRANSFORM;

		 if(LNumGeometriesChanged) E3dNodeHrc_UpdateForDisplay(LRootNode, E3duf_SHAPE);

		 LTotalNumOfGeometriesChanged += LNumGeometriesChanged;
	 }
*/
	}

	E3dWindows_RedrawByChangeFlags(LScene, LChanged);
	E3dTransformPanel_Update(NULL,  E3dDoALL, E3dDoALL, E3dDoALL,  E3dpDO_NOT_CHANGE,  E3dTrPanelALL);

	switch(LTotalNumOfGeometriesChanged)
	{
		case 0:   break;
		case 1:   E3dp_PrintMessage(ELogMESSAGE, 5000, "Froze rotation and scaling on 1 Geometry");break;
		default:  E3dp_PrintMessage(ELogMESSAGE, 5000, "Froze rotation and scaling on %d Geometries", LTotalNumOfGeometriesChanged);break;
	}
}


//========================================
// Freeze scaling on selection
//========================================
static void _CB_FreezeScaling(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dScene*     LScene = E3d_Scene;
	EIndex        LTotalNumOfGeometriesChanged = 0;
	EChangeFlags  LChanged = 0;

	EIndex  LRI = 0;const EIndex  LRN = LScene->RootNodes.Count;E3dNode**  LRootNodes = LScene->RootNodes.Items;for(;LRI < LRN;LRI++)
	{
		E3dNode*  LRootNode = LRootNodes[LRI];
		EIndex    LHrcNumOfGeometriesChanged = 0;

		E3dNode*  LNode = LRootNode;for(;LNode;)
		{
			if(LNode->Selection == E3dNodeSelBRANCH_ROOT)
			{
//printf("%s() %s:%d\n", __FUNCTION__, __FILE__, __LINE__);fflush(stdout);
				LHrcNumOfGeometriesChanged += E3dNodeHrc_FreezeScaling(LNode);

				LChanged |= E3dNodeHrc_GetTransformChangeFlagsByObjects(LNode) | E3dChgNodeTRANSFORM;

				LNode = E3dNode_BranchGetNodeAfter(LNode);
			}
			else LNode = LNode->Next;
		}

		LTotalNumOfGeometriesChanged += LHrcNumOfGeometriesChanged;
		if(LHrcNumOfGeometriesChanged) E3dNodeHrc_UpdateForDisplay(LRootNode, E3duf_SHAPE);
	}

	E3dWindows_RedrawByChangeFlags(LScene, LChanged);
	E3dTransformPanel_Update(NULL,  E3dDoALL, E3dDoALL, E3dDoALL,  E3dpDO_NOT_CHANGE,  E3dTrPanelALL);

	switch(LTotalNumOfGeometriesChanged)
	{
		case 0:   E3dp_PrintMessage(ELogMESSAGE, 5000, "Nothing changed");break;
		case 1:   E3dp_PrintMessage(ELogMESSAGE, 5000, "Froze scaling on 1 Geometry");break;
		default:  E3dp_PrintMessage(ELogMESSAGE, 5000, "Froze scaling on %d Geometries", LTotalNumOfGeometriesChanged);break;
	}
}


enum
{
	CR_SET_ORIGIN_AT_CENTER = XeCR_CUSTOM0,
	CR_SET_ORIGIN_AT_BOTTOM,
	CR_SET_ORIGIN_AT,
	CR_Y_CENTER
};

static XeWidget       _DialogW = NULL;
static EPluginLock*   _DialogLock = NULL;


static XeWidget       _YOriginAtCenterBW = NULL, _YOriginAtBottomBW = NULL, _YOriginAtBW = NULL;

static E3dCoordinate  _YCenter = 0.0;
static short          _YCentering = E3dCENTER;


//========================================================================
// "Move the Node's local origin to the centroid of its Geometries
//========================================================================
static void _OriginToCenter(void)
{
	E3dScene*     LScene = E3d_Scene;
	E3dNode*      LRootNode;
	EChangeFlags  LChanged = 0;

	EIndex  LRI = 0;const EIndex  LRN = LScene->RootNodes.Count;E3dNode**  LRootNodes = LScene->RootNodes.Items;for(;LRI < LRN;LRI++)
	{
		E3dNode*      LNode = LRootNode = LRootNodes[LRI];
		EChangeFlags  LChgF = 0;

		if(LNode->Selection != E3dNodeSelNONE)
		{
			E3dModel*	LModel = E_DynamicCast(LNode, E3dModel);

//printf("%s()-00 [%s] {%s}\n", __FUNCTION__, LNode->Name, LNode->Class->Name);fflush(stdout);
			if(LModel)
			{
				E3dMatrix      LMatrix;
				E3dBBox        LBBox;

//printf("%s()-01 [%s]\n", __FUNCTION__, LNode->Name);fflush(stdout);
				const EBool  LNodeSelected = E3dNode_IsSelected(LNode);

				E3dModel_GetBoundingBox(LModel, E3dBB_SELECTED_GEOMETRY, FALSE,  &LBBox);

// Get center of bounding box of all selected Geometries on the Model
//
				const E3dCoordinate  LOffsetX = (LBBox.Max.X - LBBox.Min.X) * 0.5 + LBBox.Min.X;

				E3dCoordinate  LOffsetY;
				switch(_YCentering)
				{
					default:      LOffsetY = (LBBox.Max.Y - LBBox.Min.Y) * 0.5 + LBBox.Min.Y;break;
					case E3dMIN:  LOffsetY = LBBox.Min.Y;break;
					case E3dVAL:  LOffsetY = _YCenter;break;
				}

				const E3dCoordinate  LOffsetZ = (LBBox.Max.Z - LBBox.Min.Z) * 0.5 + LBBox.Min.Z;

				E3dMatrix_Copy(LModel->LocalToWorldMatrix, LMatrix);
				LMatrix[M30] = LMatrix[M31] = LMatrix[M32] = 0.0;
				E3dMatrix_Translate(LMatrix,  LOffsetX, LOffsetY, LOffsetZ);
				LModel->TransformSet.Translation.X += LMatrix[M30];
				LModel->TransformSet.Translation.Y += LMatrix[M31];
				LModel->TransformSet.Translation.Z += LMatrix[M32];

				E3dMatrix_LoadIdentity(LMatrix);
				do
				{
					EIndex  LObjI = 0;const EIndex  LObjN = LModel->Objects.Count;E3dObject**  LObjects = LModel->Objects.Items;for(;LObjI < LObjN;LObjI++)
					{
						E3dObject*  LObject = LObjects[LObjI];

						if(EObject_IsSubClass(LObject, E3dGeometry))
						{
							E3dGeometry*  LGeometry = (E3dGeometry*)LObject;

							if(LNodeSelected || (LNode->Selection == E3dNodeSelOBJECT && LGeometry->Selection == E3dObjectSelOBJECT))
							{
								E3dGeometry_GetBoundingBox(LGeometry, E3dBB_ALL,  &LBBox);
//printf("[%s] BB: %f,%f,%f-%f,%f,%f\n", LGeometry->Name, LBBMin.X, LBBMin.Y, LBBMin.Z, LBBMax.X, LBBMax.Y, LBBMax.Z);fflush(stdout);
								LMatrix[M30] = -LOffsetX;
								LMatrix[M31] = -LOffsetY;
								LMatrix[M32] = -LOffsetZ;
								E3dGeometry_Transform(LGeometry, LMatrix);

								LChgF |= E3dObject_UpdateForDisplay(LObject,  E3duf_SHAPE, 0,  LScene);
								LChgF |= E3dObject_GetChangeFlagsByClass(LObject) | E3dChgNodeTRANSFORM;
								LChanged |= LChgF;
							}
						}
					}
					LNode = LNode->Next;
				} while(LNode);
			}
		}

		if(LChgF)
		{
			E3dNodeHrc_UpdateMatrices(LRootNode, TRUE);
			E3dNodeHrc_CallNodeInfoUpdateProcs(LRootNode, LChgF, FALSE);
		}
	}

	if(LChanged)
	{
		E3dWindows_RedrawByChangeFlags(LScene, LChanged);
		E3dTransformPanel_Update(NULL,  E3dDoALL, E3dDoALL, E3dDoALL,  E3dpDO_NOT_CHANGE,  E3dTrPanelALL);
	}
}


//========================================
// Dialog box callback
//========================================
static void _DCB_OriginToCenterClose(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	XeCallbackStruct*  LCBS = (XeCallbackStruct*)PCallData;
	if(LCBS->Reason == XeCR_PROTOCOLS)
	{
		XeWidget_Unmap(_DialogW);
		EPluginLock_Del(_Plugin, &_DialogLock, FALSE);
	}
}


//========================================
// Dialog box callback
//========================================
static void _DCB_OriginToCenter(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	switch((EPointerInt)PClientData)
	{
		case XeCR_OK:
			_OriginToCenter();
			XeWidget_Unmap(_DialogW);
			EPluginLock_Del(_Plugin, &_DialogLock, FALSE);
		break;

		case CR_SET_ORIGIN_AT_CENTER:
			if(((XeToggleButtonCallbackStruct*)PCallData)->Set) _YCentering = E3dCENTER;
		break;

		case CR_SET_ORIGIN_AT_BOTTOM:
			if(((XeToggleButtonCallbackStruct*)PCallData)->Set) _YCentering = E3dMIN;
		break;

		case CR_SET_ORIGIN_AT:
			if(((XeToggleButtonCallbackStruct*)PCallData)->Set) _YCentering = E3dVAL;
		break;

		case CR_Y_CENTER:
			_YCenter = ((XeScaleCallbackStruct*)PCallData)->Value.m_Coordinate;
			_YCentering = E3dVAL;
			XeToggleButton_SetState(_YOriginAtCenterBW, FALSE, FALSE);
			XeToggleButton_SetState(_YOriginAtBottomBW, FALSE, FALSE);
			XeToggleButton_SetState(_YOriginAtBW, TRUE, FALSE);
		break;
	}
}


static int _DialogLockDel(EPlugin* PPlugin, EPluginLock* PLock)
{
	XeWidget_Unmap(_DialogW);

	return(E_SUCCESS);
}


//========================================
// Popup settings dialog box
//========================================
static void _OriginToCenterDialogPopUp(void)
{
	if(_DialogLock == NULL) _DialogLock = EPluginLock_Add(_Plugin, "OriginToCenterDialog", _DialogLockDel);

	if(_DialogW == NULL)
	{
		EArg  Args[256];int  ArgCnt;

		EXeStart;
		EXeSetArg(XeNAllowShellResize, TRUE);
		EXeSetArg(XeNButtonsStretch, FALSE);
		EXeSetArg(XeNWorkAreaType, XeMATRIX);
		EXeSetArg(XeNWMDecorations, XeWMDecorCLOSE|XeWMDecorBORDER|XeWMDecorRESIZEH|XeWMDecorTITLE);
		EXeSetArg(XeNTitle, "Origin to center v0.4.0");EXeSetArg(XeNIconName, "Origin to ctr");
		XeWidget  LDialogW = XeDialog_New(XeDIALOG_TEMPLATE, "Origin to ctr", E3dp_TopShellW,  Args, ArgCnt);if(LDialogW == NULL) return;
		XeAddCallback(LDialogW, XeNWMCallback, _DCB_OriginToCenterClose, (EPointer)NULL);

		_DialogW = LDialogW;

		XeWidget  LMessageBoxW = XeDialog_GetChild(LDialogW, XeDIALOG_MAIN);
		EXeStart;
		EXeSetArg(XeNOrientation, XeVERTICAL);
		EXeSetArg(XeNWorkAreaMarginSizeX, 6);EXeSetArg(XeNWorkAreaMarginSizeY, 6);
		Xe_SetValues(LMessageBoxW, Args, ArgCnt);
		{ XeWidget  LW = XeDialog_AddButton("OK",    XePushButton_class,   LMessageBoxW, TRUE);XeAddCallback(LW, XeNActivateCallback, _DCB_OriginToCenter, (EPointer)XeCR_OK); }

		XeWidget  LTopMatrixW = XeDialog_GetChild(LDialogW, XeDIALOG_WORK_AREA);
		EXeStart;
		EXeSetArg(XeNOrientation, XeVERTICAL);
		EXeSetArg(XeNSpacingY, 4);
		Xe_SetValues(LTopMatrixW, Args, ArgCnt);

		EXeStart;
		EXeSetArg(XeNRadioBehavior, TRUE);
		EXeSetArg(XeNOrientation, XeVERTICAL);
		EXeSetArg(XeNSpacingY, 2);
		EXeSetArg(XeNMarginSizeX, 0);EXeSetArg(XeNMarginSizeY, 0);
		EXeSetArg(XeNBackgroundColorPtr, &Xe_BackgroundRGBAiColor);
		XeWidget  LMatrixW = XeWidget_ManagedNew("", XeMatrixWidget_class, LTopMatrixW,  Args, ArgCnt);

		XeWidget  LW;

		EXeStart;
		EXeSetArg(XeNBackgroundColorPtr, &Xe_BackgroundRGBAiColor);
		EXeSetArg(XeNSelectColorPtr, &Xe_HighlightRGBAiColor);
		EXeSetArg(XeNShadowThickness, 0);
		EXeSetArg(XeNHighlightThickness, 0);
		EXeSetArg(XeNFontList, Xe_LabelFontList);
		EXeSetArg(XeNSet, (EBool)(_YCentering  == E3dCENTER));
		EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNAlignmentX, XeAlignBEGINNING);
		LW = _YOriginAtCenterBW = XeWidget_ManagedNew("Y origin at Y center", XeToggleButton_class, LMatrixW,  Args, ArgCnt);
		XeAddCallback(LW, XeNValueChangedCallback, _DCB_OriginToCenter, (EPointer)CR_SET_ORIGIN_AT_CENTER);

		EXeStart;
		EXeSetArg(XeNBackgroundColorPtr, &Xe_BackgroundRGBAiColor);
		EXeSetArg(XeNSelectColorPtr, &Xe_HighlightRGBAiColor);
		EXeSetArg(XeNShadowThickness, 0);
		EXeSetArg(XeNHighlightThickness, 0);
		EXeSetArg(XeNFontList, Xe_LabelFontList);
		EXeSetArg(XeNSet, (EBool)(_YCentering == E3dMIN));
		EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNAlignmentX, XeAlignBEGINNING);
		LW = _YOriginAtBottomBW = XeWidget_ManagedNew("Y origin at bottom", XeToggleButton_class, LMatrixW,  Args, ArgCnt);
		XeAddCallback(LW, XeNValueChangedCallback, _DCB_OriginToCenter, (EPointer)CR_SET_ORIGIN_AT_BOTTOM);

		EXeStart;
		EXeSetArg(XeNBackgroundColorPtr, &Xe_BackgroundRGBAiColor);
		EXeSetArg(XeNSelectColorPtr, &Xe_HighlightRGBAiColor);
		EXeSetArg(XeNShadowThickness, 0);
		EXeSetArg(XeNHighlightThickness, 0);
		EXeSetArg(XeNFontList, Xe_LabelFontList);
		EXeSetArg(XeNSet, (EBool)(_YCentering == E3dVAL));
		EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNAlignmentX, XeAlignBEGINNING);
		LW = _YOriginAtBW = XeWidget_ManagedNew("Y origin at:", XeToggleButton_class, LMatrixW,  Args, ArgCnt);
		XeAddCallback(LW, XeNValueChangedCallback, _DCB_OriginToCenter, (EPointer)CR_SET_ORIGIN_AT);


		EXeStart;
		EXeSetArg(XeNScaleLengthInPixels, 120);
		EXeSetArg(XeNTextFieldDigits, 8);EXeSetArg(XeNDecimalPoints, 5);
		LW = XeScale_New_Coord(LTopMatrixW, "", Args, ArgCnt);
		XeScale_SetValueAndRanges_Coord(LW,  _YCenter,  -1000.0, 1000.0,  -10.0, 10.0,  FALSE);
		XeAddCallback(LW, XeNValueChangedCallback, _DCB_OriginToCenter, (EPointer)CR_Y_CENTER);

		XeShell_Raise(_DialogW);
		XeShell_CenterToWidget(_DialogW, E3dp_TopShellW);
	}
	else XeShell_Raise(_DialogW);
}


//========================================================================
// "Move the Model's local origin to the centroid of its Geometries
//========================================================================
static void _CB_OriginToCenter(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	if(((XePushButtonCallbackStruct*)PCallData)->Reason == XeCR_ACTIVATE_OPTIONS) { _OriginToCenterDialogPopUp();return; }
	_OriginToCenter();
}


typedef struct
{
	EOperatorCore;

// E3dOperatorSnapPoints part
//
	E3dCoord3           GridSize;
	unsigned int        TransformAxes;
	E3dShapeSnapshot**  ShapeSnapshots;EIndex  NumOfShapeSnapshots;
} E3dOperatorSnapPoints;


//================================================
// Snap tags to grid Operator proc
//================================================
static EChangeFlags _SnapPoints_Func(EOps* POps, EOperator* POperator, const int POperation)
{
	E3dScene*               LScene = E3d_Scene;
	E3dOperatorSnapPoints*  LOperator = (E3dOperatorSnapPoints*)POperator;

	const EIndex  LShapeN = LOperator->NumOfShapeSnapshots;
	switch(POperation)
	{
		case EOpDO:
			{
				EListAI_s(, EOpNode, LOpNodes);

				E3dCoordinate  LGridSizeX = LOperator->GridSize.X, LGridSizeY = LOperator->GridSize.Y, LGridSizeZ = LOperator->GridSize.Z;
				EBool          LGridSnapX = FALSE, LGridSnapY = FALSE, LGridSnapZ = FALSE;

				if(LOperator->TransformAxes & E3dDoX) LGridSnapX = TRUE;
				if(LOperator->TransformAxes & E3dDoY) LGridSnapY = TRUE;
				if(LOperator->TransformAxes & E3dDoZ) LGridSnapZ = TRUE;

				EChangeFlags  LChanged = 0;

				EIndex  LObjI = 0;for(LObjI = 0;LObjI < LShapeN;LObjI++)
				{
					E3dShapeSnapshot*  LShapeSnapshot = LOperator->ShapeSnapshots[LObjI];
					EObject*           LObject = LShapeSnapshot->Object;
					const EBool        LLocalToWorldMatrixIsNotIdentity = LShapeSnapshot->LocalToWorldMatrixIsNotIdentity;

					E3dMatrix  LLocalToWorldMatrix;E3dMatrix_LoadIdentity(LLocalToWorldMatrix);
					E3dMatrix  LWorldToLocalMatrix;E3dMatrix_LoadIdentity(LWorldToLocalMatrix);

					if(LLocalToWorldMatrixIsNotIdentity)
					{
						E3dMatrix_Copy(LShapeSnapshot->LocalToWorldMatrix, LLocalToWorldMatrix);
						E3dMatrix_Invert3x4(LLocalToWorldMatrix, LWorldToLocalMatrix);
					}

					if(EObject_IsSubClass(LObject, E3dSpline))
					{
						const E3dSplineCVsSnapshot*  LSnapshot = (E3dSplineCVsSnapshot*)LShapeSnapshot;
						const E3dSnapshotSplineCV*   LSnapshotCV = LSnapshot->SnapshotCVs;
						E3dSpline*                   LSpline = (E3dSpline*)LObject;
						E3dSplineCV*                 LCVs = LSpline->CVs.Items;

						EIndex  LI = 0;EIndex  LVN = LSnapshot->CVs_Count;for(;LI < LVN;LI++, LSnapshotCV++)
						{
							E3dSplineCV*  LCV = LCVs + LSnapshotCV->Index;
							E3dCoord3     LPosition, LPrevious, LNext;

							if(LLocalToWorldMatrixIsNotIdentity)
							{
								EMatrix_Transform3x4_vv(LLocalToWorldMatrix, LCV->Position,  LPosition);
								EMatrix_Transform3x4_vv(LLocalToWorldMatrix, LCV->Previous,  LPrevious);
								EMatrix_Transform3x4_vv(LLocalToWorldMatrix, LCV->Next,  LNext);
							}
							else
							{
								LPosition = LCV->Position;
								LPrevious = LCV->Previous;
								LNext = LCV->Next;
							}

							const E3dCoord3  LOldPosition = LPosition;

							if(LGridSnapX) LPosition.X = E3d_SnapCoordinateTo(LPosition.X, LGridSizeX);
							if(LGridSnapY) LPosition.Y = E3d_SnapCoordinateTo(LPosition.Y, LGridSizeY);
							if(LGridSnapZ) LPosition.Z = E3d_SnapCoordinateTo(LPosition.Z, LGridSizeZ);

							if(LLocalToWorldMatrixIsNotIdentity)
							{
								EMatrix_Transform3x4_vv(LWorldToLocalMatrix,  LPosition,  LCV->Position);

// Apply the differentials to the tangent end points.
// We always update these, as the user might switch back-and-forth between e.g. Bezier and linear Spline type.
//
								{
									const E3dCoord3  E_Sub3i_vv(LDiff, LPosition, LOldPosition);

									E_Add3_vvv(LPrevious, LDiff, LPrevious);
									E_Add3_vvv(LNext,     LDiff, LNext);

									EMatrix_Transform3x4_vv(LWorldToLocalMatrix,  LPrevious,  LCV->Previous);
									EMatrix_Transform3x4_vv(LWorldToLocalMatrix,  LNext,      LCV->Next);
								}
							}
							else
							{
								LCV->Position = LPosition;

// Apply the differentials to the tangent end points.
// We always update these, as the user might switch back-and-forth between e.g. Bezier and linear Spline type.
//
								{
									const E3dCoord3  E_Sub3i_vv(LDiff, LPosition, LOldPosition);

									if(LGridSnapX) { LPrevious.X += LDiff.X;LNext.X += LDiff.X; }
									if(LGridSnapY) { LPrevious.Y += LDiff.Y;LNext.Y += LDiff.Y; }
									if(LGridSnapZ) { LPrevious.Z += LDiff.Z;LNext.Z += LDiff.Z; }

									LCV->Previous = LPrevious;
									LCV->Next = LNext;
								}
							}
						}

						LChanged |= E3dSpline_Changed(LSpline, E3dChgSplineSHAPE,  &LOpNodes);
					}
					else if(EObject_IsSubClass(LObject, E3dMesh))
					{
						const E3dVerticesSnapshot*  LSnapshot = (E3dVerticesSnapshot*)LShapeSnapshot;
						const E3dVertexSnapshot*    LVertexSnapshot = (E3dVertexSnapshot*)LSnapshot->VertexSnapshots;const EIndex  LVertexSnapshotSize = LSnapshot->VertexSnapshotSize;
						E3dMesh*                    LMesh = (E3dMesh*)LObject;
						E3dVertex*                  LVertices = LMesh->Vertices.Items;const E3dVertexSize  LVertexSize = LMesh->VertexSize;

						EIndex  LVI = 0;EIndex  LVN = LSnapshot->VertexSnapshots_Count;for(;LVI < LVN;LVI++, EStruct_Inc(LVertexSnapshot, LVertexSnapshotSize))
						{
							E3dVertex*  LVertex = EStruct_ByIndex(LVertices, LVertexSize, LVertexSnapshot->Index);
							E3dCoord3   L;

							if(LLocalToWorldMatrixIsNotIdentity) EMatrix_Transform3x4_vv(LLocalToWorldMatrix, LVertex->Position,  L);
							else L = LVertex->Position;

							if(LGridSnapX) L.X = E3d_SnapCoordinateTo(L.X, LGridSizeX);
							if(LGridSnapY) L.Y = E3d_SnapCoordinateTo(L.Y, LGridSizeY);
							if(LGridSnapZ) L.Z = E3d_SnapCoordinateTo(L.Z, LGridSizeZ);

							if(LLocalToWorldMatrixIsNotIdentity) EMatrix_Transform3x4_vv(LWorldToLocalMatrix,  L,  LVertex->Position);
							else LVertex->Position = L;
						}

						E3dObject_UpdateForDisplay((E3dObject*)LObject,  E3dUpdateMeshSHAPE, 0,  LScene);

						EOpNode*  LOpNode = EList_StructAddAClr_s(LOpNodes, 4);if(LOpNode)
						{
							LOpNode->Object = (ECoreObject*)LObject;
							LOpNode->Changed = E3dChgMeshSHAPE;
						}

						LChanged |= E3dChgMeshSHAPE;
					}
					else
					{
printf("%s() %s:%d  %sIMPLEMENT ME%s  {%s}\n", __FUNCTION__, __FILE__, __LINE__, ECodeYellow, ECodeNormal, LObject->Class->Name);fflush(stdout);
					}
				}

				if(LOpNodes.Items)
				{
					LChanged |= E3dObjects_CallOutputs(ECbINTERNAL|ECbUSER,  &LOpNodes,  NULL);
					EList_FreeA_s(LOpNodes);
				}

				return(LChanged);
			}

		case EOpUNDO:
		return(E3dShapeSnapshots_UndoRedo(LOperator->ShapeSnapshots, LOperator->NumOfShapeSnapshots,  POperation));
	}
	return(0);
}


static void _SnapPoints_Init(EOperator* POperator)
{
	E3dOperatorSnapPoints*  LOperator = (E3dOperatorSnapPoints*)POperator;

	LOperator->GridSize.X = 1.0;
	LOperator->GridSize.Y = 1.0;
	LOperator->GridSize.Z = 1.0;
	LOperator->TransformAxes = 0;
	LOperator->NumOfShapeSnapshots = 0;
	LOperator->ShapeSnapshots = NULL;
}


static int _SnapPoints_Free(EOperator* POperator)
{
	E3dOperatorSnapPoints*	LOperator = (E3dOperatorSnapPoints*)POperator;

	E3dShapeSnapshots_Free(&LOperator->ShapeSnapshots, &LOperator->NumOfShapeSnapshots);

	return(E_SUCCESS);
}


//========================================
// Snap selected points to the grid
//========================================
static void _CB_SnapSelectedPointsToGrid(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dScene*  LScene = E3d_Scene;
	EIndex     LShapeSnapshots_Count;E3dShapeSnapshot**  LShapeSnapshots = E3dScene_CollectTransformPoints(LScene, &LShapeSnapshots_Count, FALSE, TRUE);

	if(LShapeSnapshots_Count > 0)
	{
		E3dWindow*        L3DWindow = E3dp_Main3DWindow;
		E3dGridSettings*  LGridSettings = &L3DWindow->GridSettings;
		uint32_t          LTranslateAxes = 0;

// Store Point (Vertex, Spline key etc.) positions for undo
//
		E3dOperatorSnapPoints*  LOperator = (E3dOperatorSnapPoints*)EOperator_New(_OperatorClass);
		EObject_SetName(LOperator, EStrDup("Snap points to grid"), FALSE);

		E3dpWindow*         L3DpWindow = E_DynamicCast(L3DWindow, E3dpWindow);if(L3DpWindow == NULL) return;
		E3dTransformPanel*  LTransformPanel = L3DpWindow->Panel->TransformPanel;if(LTransformPanel == NULL) return;
		if(LTransformPanel->TransformMode != E3dTmLOCAL)
		{
			EIndex  LI = 0;for(;LI < LShapeSnapshots_Count;LI++)
			{
				E3dShapeSnapshot*  LShapeSnapshot = LShapeSnapshots[LI];
				EObject*           LObject = LShapeSnapshot->Object;if(LObject && EObject_IsSubClass(LObject, E3dObject))
				{
					E3dModel*  LModel = E3dObject_GetSelectingModel((E3dObject*)LObject, TRUE);if(LModel)
					{
						E3dMatrix_Copy(LModel->LocalToWorldMatrix, LShapeSnapshot->LocalToWorldMatrix);
						LShapeSnapshot->LocalToWorldMatrixIsNotIdentity = TRUE;
					}
				}
			}
		}

		if(LGridSettings->GridSnapToX) LTranslateAxes |= E3dDoX;
		if(LGridSettings->GridSnapToY) LTranslateAxes |= E3dDoY;
		if(LGridSettings->GridSnapToZ) LTranslateAxes |= E3dDoZ;
		LOperator->GridSize = LGridSettings->GridSnapSize;
		LOperator->TransformAxes = LTranslateAxes;
		LOperator->ShapeSnapshots = LShapeSnapshots;LOperator->NumOfShapeSnapshots = LShapeSnapshots_Count;

		E3dpUndoStack_AddOperator((EOperator*)LOperator, E3dLng_Snap_tagged_points_to_grid);

		E3dWindows_RedrawByChangeFlags(LScene, _SnapPoints_Func(NULL, (EOperator*)LOperator, EOpDO));
	}
}


//========================================
// Entry point of the plugin
//========================================
int E_PluginInit(EPlugin* PPlugin)
{
	{
		EOperatorClass  LClass[] =
		{
			{
				.Name=        "Snap points to grid",
				.StructSize=  sizeof(E3dOperatorSnapPoints),
				.Init=        _SnapPoints_Init,
				.Free=        _SnapPoints_Free,
				.Func=        _SnapPoints_Func
			}
		};
		_OperatorClass = EOperator_ClassRegister(LClass);
	}

	_Plugin = PPlugin;

	_MenuButtons[_MenuButtons_Count++] = XePushButton_Add(Xe_App, "Menu->Transform", "Freeze to world", '\0', NULL, NULL, FALSE, NULL, _CB_FreezeToWorld, (EPointer)0);
	_MenuButtons[_MenuButtons_Count++] = XePushButton_Add(Xe_App, "Menu->Transform", "Freeze branch to its parent", '\0', NULL, NULL, FALSE, NULL, _CB_FreezeBranchToItsParent, (EPointer)0);
	_MenuButtons[_MenuButtons_Count++] = XePushButton_Add(Xe_App, "Menu->Transform", "Freeze branch to its root", '\0', NULL, NULL, FALSE, NULL, _CB_FreezeBranchToItsRoot, (EPointer)0);
	_MenuButtons[_MenuButtons_Count++] = XePushButton_Add(Xe_App, "Menu->Transform", "Freeze rotation on branch", '\0', NULL, NULL, FALSE, NULL, _CB_FreezeRotation, (EPointer)0);
	_MenuButtons[_MenuButtons_Count++] = XePushButton_Add(Xe_App, "Menu->Transform", "Freeze scaling on branch", '\0', NULL, NULL, FALSE, NULL, _CB_FreezeScaling, (EPointer)0);
	_MenuButtons[_MenuButtons_Count++] = XePushButton_Add(Xe_App, "Menu->Transform", "Snap tagged points to grid", '\0', NULL, NULL, FALSE, NULL, _CB_SnapSelectedPointsToGrid, (EPointer)0);


	_ToolBarButtons[_ToolBarButtons_Count++] = XePushButton_Add(Xe_App, "Tool->Transform", "Freeze to world", '\0', NULL, NULL, FALSE, "Perform the local-to-world transformations on the Geometries of the selected Models,\nthen reset the transformation values of the Models.", _CB_FreezeToWorld, (EPointer)0);
	_ToolBarButtons[_ToolBarButtons_Count++] = XePushButton_Add(Xe_App, "Tool->Transform", "Freeze rotation", '\0', NULL, NULL, FALSE, "'Bake' rotations and scaling into the Geometries of the selected Models, then\nreset the rotation and scaling values of the Models (leaving only translation).", _CB_FreezeRotation, (EPointer)0);
	_ToolBarButtons[_ToolBarButtons_Count++] = XePushButton_Add(Xe_App, "Tool->Transform", "Freeze scaling", '\0', NULL, NULL, FALSE, "'Bake' scaling into the Geometries of the selected Models,\nthen reset the scaling values of the Models.", _CB_FreezeScaling, (EPointer)0);
	_ToolBarButtons[_ToolBarButtons_Count++] = XePushButton_Add(Xe_App, "Tool->Transform", "Origin to ctr", '\0', NULL, NULL, FALSE, "Move the local origin of Model(s) to the center of their selected Geometries.", _CB_OriginToCenter, (EPointer)0);

	return(E_SUCCESS);
}


//========================================
// Exit method of the plugin
//========================================
int E_PluginExit(const EBool PExiting)
{
	if(!PExiting || Xe_GUIFreeOnExit)
	{
		{ int  LI = 0;for(;LI < _MenuButtons_Count;LI++) { XeItemFree(_MenuButtons[LI]); } _MenuButtons_Count = 0; }
		{ int  LI = 0;for(;LI < _ToolBarButtons_Count;LI++) { XeItemFree(_ToolBarButtons[LI]); } _ToolBarButtons_Count = 0; }
	}

	if(_OperatorClass) { EOperator_ClassDeactivate(_OperatorClass);_OperatorClass = NULL; }

	return(E_SUCCESS);
}
