//================================================================================================================================
// Plugin for EQUINOX-3D
//
// This sample code shows how to:
//  - Create a simple dialog box with a button. When the button is pressed:
//  - Look up runtime Object classes by name, in this case, the primitive shapes, "Box", "Sphere" and "Cylinder".
//  - Create Objects of those classes
//  - Build a simple Model hierarchy, using those primitive shapes
//  - Set transform values
//  - Add the hierarchy to the Scene, and redraw the 3DWindows
//
// A note on transforms:
// libE3D / EQUINOX-3D have the most powerful transform system that we are aware of:
// in addition to traditional scale, rotate and translate, it also allows for an arbitrary list of transforms on each Model.
// For example, a single Model can have 4 rotations, 2 scale operations, 5 translates, then 2 more scales, etc.
// Transforms can have a name and a semantic string.
// So, unlike any other 3D software, libE3D / EQUINOX-3D can accurately import / export transforms to / from any other system.
// For example, Maya and 3DS Max have very different transform system, and there's no way to accurately exchange hierarchies
// between those.
// However, libE3D / EQUINOX-3D can import and export accurate transform setups with either of those applications.
// This example shows how to use the traditional transform system.
//
// To avoid cluttering the UI, example plugins are not loaded by default, when EQUINOX-3D is started.
// You can manually load them from the Plugin manager, or you can edit your Plugins/Plugins.list file,
// and comment out (e.g. with a trailing "#") or remove the "Load = FALSE;" line for a plugin.
//
// AUTHOR:  Gabor Nagy
// DATE:    2026-Jan-07 02:46:09
//
// Copyright (C) 2014-2026 by Gabor Nagy. All rights reserved.
//================================================================================================================================
#include <assert.h>
#include <stdio.h>

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

#include <E3D/Scene.h>

#include <E3D/UI/Panel.h>

#include <Xe/PushButton.h>
#include <Xe/Shell.h>
#include <Xe/Dialog.h>


char*                E_PluginInfo = "Sample code: build hierarchy, with runtime-defined primitive shape classes.";

static EPlugin*      _Plugin = NULL;

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

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



enum
{
	CR_TEST = XeCR_CUSTOM0,
};


//========================================
// Dialog box callback
//========================================
static void _DCB(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dScene*  LScene = E3d_Scene;

// Create customized primitives (Box, Cylinder, Torus).
//
	{
// Look up the classes. Bail, if one or more of the classes don't exist (e.g. the appropriate plugin is not loaded)
// We only need to do this once, so these could even be global variables in your plugin.
//
		E3dObjectComponentClass*  LBoxClass = E3dObjectComponent_ClassByName("Box");if(LBoxClass == NULL) return;
		E3dObjectComponentClass*  LCylinderClass = E3dObjectComponent_ClassByName("Cylinder");if(LCylinderClass == NULL) return;
		E3dObjectComponentClass*  LSphereClass = E3dObjectComponent_ClassByName("Sphere");if(LSphereClass == NULL) return;
		E3dObjectComponentClass*  LTorusClass = E3dObjectComponent_ClassByName("Torus");if(LTorusClass == NULL) return;

		{
// Create ObjectComponent templates. These are used for setting user-adjustable parameters before creating an Object.
// These can be reused.
//
			E3dObjectComponent*  LBoxInfo = E3dObjectComponent_New(LBoxClass);
			E3dObjectComponent*  LCylinderInfo = E3dObjectComponent_New(LCylinderClass);
			E3dObjectComponent*  LSphereInfo = E3dObjectComponent_New(LSphereClass);
			E3dObjectComponent*  LTorusInfo = E3dObjectComponent_New(LTorusClass);


			{
// Set Resources by name.
// We use the Resource system, as we don't know the memory layout of runtime-typed objects, outside the plugin that implements them.
//
// Alternatively, if you set a Resource frequently (thousands of times), you could do this:
//
// Look up Resource by name once and get handle:
//  EResource*     LRadiusResource = EObject_ResourceByName(LInfo, "Radius");
//
// Set value using the handle:
//  EResource_Set1f(LInfo, LRadiusResource, 2.0);
// This avoids the name-look-ups, so it's faster.
//


// Create an empty Model for the root of our hierarchy. This is how you create any object in the "E_" object-oriented system, from EpImage, to E3dModel.
//
				E3dModel*  LRootModel;E_NewNamed(E3dModel, "Bicycle",  LRootModel);
				LRootModel->TransformSet.EulerRotation.Angles.Y = -50.0;


// Create a torus for the wheels. We'll instantiate the same torus Mesh for both wheels.
				EObject_NamedResourceSet1f(LTorusInfo, "MainRadius", 0.5);
				EObject_NamedResourceSet1f(LTorusInfo, "CircleRadius", 0.04);
				EObject_NamedResourceSet1i(LTorusInfo, "MainSections", 30);      // Set the resolution along the main circle
				EObject_NamedResourceSet1i(LTorusInfo, "CircleSections", 16);    // Set the resolution along the small circle
				E3dObject*  LWheel = E3dObject_CustomNewInit(LScene, LTorusInfo, TRUE);

// Create a Model for the rear wheel, add our wheel Mesh to it, then add this Model to the root Model.
// This shows that, instead of using E_NewNamed(), you can set the object name separately, with EObject_SetName().
// This is how you change the name of any existing Object.
//
				E3dModel*  LModel;E_New(E3dModel, LModel);EObject_SetName(LModel, EStrDup("RearWheel"), FALSE);
				LModel->TransformSet.Translation.Y =  0.54;
				LModel->TransformSet.Translation.Z = -0.8;
				LModel->TransformSet.EulerRotation.Angles.Z = 90.0;
				E3dModel_ObjectAppend(LModel, LWheel, FALSE);
				E3dNode_AddChild((E3dNode*)LRootModel, (E3dNode*)LModel);


				{
// Create a cylinder Geometry for parts of the bycicle frame. We'll reuse this, by scaling it to different lengths.
					EObject_NamedResourceSet1f(LCylinderInfo, "Radius", 0.03);
					EObject_NamedResourceSet1f(LCylinderInfo, "Height", 1.0);
					EObject_NamedResourceSet1i(LCylinderInfo, "OrigAtBottom", TRUE);
					E3dObject*  LFramePart = E3dObject_CustomNewInit(LScene, LCylinderInfo, TRUE);

// Create Models (transform-nodes) for the frame
					E_NewNamed(E3dModel, "Top tube",  LModel);E3dModel_ObjectAppend(LModel, LFramePart, FALSE);
					LModel->TransformSet.Scaling = E3dCoord3_s(1.0, 1.06, 1.0);
					LModel->TransformSet.EulerRotation.Angles.X = 90.0;
					LModel->TransformSet.Translation = E3dCoord3_s(0.0, 1.22, -0.4);  // This is a macro for setting the X,Y and Z coordinates of a vector
					E3dNode_AddChild((E3dNode*)LRootModel, (E3dNode*)LModel);

					E_NewNamed(E3dModel, "Seat tube",  LModel);E3dModel_ObjectAppend(LModel, LFramePart, FALSE);
					LModel->TransformSet.EulerRotation.Angles.X = -30.0;
					LModel->TransformSet.Translation = E3dCoord3_s(0.0, 0.52, 0.0);
					E3dNode_AddChild((E3dNode*)LRootModel, (E3dNode*)LModel);

					E_NewNamed(E3dModel, "Down tube",  LModel);E3dModel_ObjectAppend(LModel, LFramePart, FALSE);
					LModel->TransformSet.Scaling = E3dCoord3_s(1.0, 0.86, 1.0);
					LModel->TransformSet.Translation = E3dCoord3_s(0.0, 0.54, 0.0);
					LModel->TransformSet.EulerRotation.Angles.X = 38.0;
					E3dNode_AddChild((E3dNode*)LRootModel, (E3dNode*)LModel);

					E_NewNamed(E3dModel, "Seatstays",  LModel);E3dModel_ObjectAppend(LModel, LFramePart, FALSE);
					LModel->TransformSet.Scaling = E3dCoord3_s(1.0, 0.8, 1.0);
					LModel->TransformSet.EulerRotation.Angles.X = 32.0;
					LModel->TransformSet.Translation = E3dCoord3_s(0.0, 0.54, -0.82);
					E3dNode_AddChild((E3dNode*)LRootModel, (E3dNode*)LModel);

					E_NewNamed(E3dModel, "Chainstays",  LModel);E3dModel_ObjectAppend(LModel, LFramePart, FALSE);
					LModel->TransformSet.Scaling = E3dCoord3_s(1.0, 0.86, 1.0);
					LModel->TransformSet.EulerRotation.Angles.X = -90.0;
					LModel->TransformSet.Translation = E3dCoord3_s(0.0, 0.54, 0.02);
					E3dNode_AddChild((E3dNode*)LRootModel, (E3dNode*)LModel);

// Steerable parts
					{
						E3dModel*  LSteeringModel;E_NewNamed(E3dModel, "Steering",  LSteeringModel);E3dModel_ObjectAppend(LSteeringModel, LFramePart, FALSE);
						LSteeringModel->TransformSet.EulerRotation.Angles = E3dCoord3_s(-12.0, 20.0, 0.0);    // Turn the front wheel
						LSteeringModel->TransformSet.EulerRotation.Order = E3dXYZ;                           // We need to overwrite the default euler rotation order (ZYX), to XYZ, so the steering works correctly
						LSteeringModel->TransformSet.Translation = E3dCoord3_s(0.0, 0.54, 0.8);
						E3dNode_AddChild((E3dNode*)LRootModel, (E3dNode*)LSteeringModel);

// Another cylinder for the handle bar.
						EObject_NamedResourceSet1f(LCylinderInfo, "Radius", 0.03);
						EObject_NamedResourceSet1f(LCylinderInfo, "Height", 0.9);
						EObject_NamedResourceSet1i(LCylinderInfo, "OrigAtBottom", FALSE);  // We want this cylinder to be centered along its height
						LModel = E3dModel_CustomObjectNewInit(LScene, LCylinderInfo, NULL, "HandleBar", FALSE);
						LModel->TransformSet.Translation = E3dCoord3_s(0.0, 1.0, 0.04);
						LModel->TransformSet.EulerRotation.Angles = E3dCoord3_s(90.0, 90.0, 0.0);
						E3dNode_AddChild((E3dNode*)LSteeringModel, (E3dNode*)LModel);

// Create a Model for the front wheel
						E_NewNamed(E3dModel, "FrontWheel",  LModel);
						LModel->TransformSet.EulerRotation.Angles.Z = 90.0;
						E3dModel_ObjectAppend(LModel, LWheel, FALSE);
						E3dNode_AddChild((E3dNode*)LSteeringModel, (E3dNode*)LModel);
					}
				}


// Create a box for the seat
				EObject_NamedResourceSet1f(LBoxInfo, "EdgeLengthX", 0.2);
				EObject_NamedResourceSet1f(LBoxInfo, "EdgeLengthY", 0.1);
				EObject_NamedResourceSet1f(LBoxInfo, "EdgeLengthZ", 0.36);
				LModel = E3dModel_CustomObjectNewInit(LScene, LBoxInfo, NULL, "Seat", FALSE);  // This function creates a custom Geometry, and a Model for it
				LModel->TransformSet.Translation = E3dCoord3_s(0.0, 1.42, -0.48);
				E3dNode_AddChild((E3dNode*)LRootModel, (E3dNode*)LModel);


// Free our Component templates
				E3dObjectComponent_Free(LBoxInfo);
				E3dObjectComponent_Free(LCylinderInfo);
				E3dObjectComponent_Free(LSphereInfo);
				E3dObjectComponent_Free(LTorusInfo);

				E3dNodeHrc_UpdateConnections((E3dNode*)LRootModel);     // Update all internal connections in our hierarchy, at once
				E3dNodeHrc_UpdateMatrices((E3dNode*)LRootModel, TRUE);  // Update the Matrices of our new hierarchy, to make the hierarcy ready for rendering

// Add our hierarcy to the main Scene
				E3dScene_NodeHrcAdd(LScene, (E3dNode*)LRootModel, E3dCallSCENE_CALLBACKS);

// These 2 calls are needed by EQUINOX-3D, so our new hierarchy will work correctly
				E3dScene_ObjectsUpdateModelList(LScene);
				E3dScene_LayoutOnSchematics(LScene, TRUE);


// This is needed, so the intelligent update system in libE3D / EQUINOX-3D knows what changed. We will be OR-ing flags to this, to indicate what we are changing in the Scene.
// In this case, we will be changing Meshes, but if we were just changing Splines, we wouldn't want all the ray-traced views to start redrawing.
				EChangeFlags  LChanged = E3dNodeHrc_GetChangeFlagsByObjects((E3dNode*)LRootModel) | E3dChgHIERARCHY;

				E3dWindows_RedrawByChangeFlags(LScene, LChanged);
			}
		}
	}
}


//========================================
// Dialog box callback
//========================================
static void _DCB_Close(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	XeCallbackStruct*  LCBS = (XeCallbackStruct*)PCallData;
	if(LCBS->Reason == XeCR_PROTOCOLS)  // Check this because this callback may receive XeCR_FULLSCREEN_OFF/ON calls, too
	{
// Un-display dialog box
//
		XeWidget_Unmap(_DialogW);

// Delete plugin lock
//
		EPluginLock_Del(_Plugin, &_DialogLock, FALSE);
	}
}


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

	return(E_SUCCESS);
}


//================================================
// ToolBar icon callback handler
//================================================
static void _CB(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
// We'll be displaying a dialog box, so we'll lock the plugin to prevent unloading it (and pulling the rag out from beneath our global variables).
// When the application exits, or if the user unloads the plugin, _DialogLockDel will get called before E_PluginExit.
// This two-pass exit (unlock & unload) of plugins is necessary to keep certain inter-dependencies from breaking.
//
	if(_DialogLock == NULL) _DialogLock = EPluginLock_Add(_Plugin, "BuildHierarchiesDialog", _DialogLockDel);

	if(_DialogW == NULL)
	{
		XeWidget LW;
		EArg     Args[32];int  ArgCnt;
		EXeStart;
		EXeSetArg(XeNAllowShellResize, TRUE);
		EXeSetArg(XeNButtonsStretch, FALSE);
		EXeSetArg(XeNWorkAreaType, XeMATRIX);
		EXeSetArg(XeNWMDecorations, XeWMDecorCLOSE|XeWMDecorBORDER|XeWMDecorRESIZEH|XeWMDecorTITLE);
		EXeSetArg(XeNTitle, "Hierarchy");EXeSetArg(XeNIconName, "Hierarchy");
		XeWidget  LDialogW = XeDialog_New(XeDIALOG_TEMPLATE, "Hierarchy", E3dp_TopShellW,  Args, ArgCnt);if(LDialogW == NULL) return;
		XeAddCallback(LDialogW, XeNWMCallback, _DCB_Close, (EPointer)NULL);

		_DialogW = LDialogW;

		XeWidget  LTopMatrixW = XeDialog_GetChild(LDialogW, XeDIALOG_WORK_AREA);

		EXeStart;
		EXeSetArg(XeNHighlightThickness, 0);
		EXeSetArg(XeNMarginSizeX, 8);EXeSetArg(XeNMarginSizeY, 8);
		EXeSetArg(XeNResizableX, TRUE);
		Xe_SetValues(LTopMatrixW, Args, ArgCnt);

		EXeStart;
		EXeSetArg(XeNHighlightThickness, 0);
		EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNAlignmentX, XeAlignCENTER);
		EXeSetArg(XeNResizableY, FALSE);
		LW = XeWidget_ManagedNew("Create primitive hierarchy", XePushButton_class, LTopMatrixW,  Args, ArgCnt);
		XeAddCallback(LW, XeNActivateCallback, _DCB, (EPointer)NULL);


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


static const char*  _ToolTip = "This plugin demonstrates how to build a hierarchy, with primitive shapes, and instancing.";

//========================================
// Entry point of the plugin
//========================================
int E_PluginInit(EPlugin* PPlugin)
{
	_Plugin = PPlugin;

// Use default plugin icon
//
	XePushButton_AddImg3(_ToolBarButtons[_ToolBarButtons_Count++], Plugin, "Tool->Create", "TestRuntimeClass", '\0', NULL, NULL, FALSE, _ToolTip, _CB, (EPointer)0);

	return(E_SUCCESS);
}


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

	return(E_SUCCESS);
}
