//================================================================
// Create a sphere and change it interactively
//
// AUTHOR:  Gabor Nagy
// DATE:    2025-Mar-12 19:05:25
//
// Copyright (C) 1996-2026 By Gabor Nagy. All rights reserved.
//================================================================
#include <math.h>
#include <stdio.h>

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

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

#include <E3D/Math.h>

#include <E3D/UI/CreateMacros.h>
#include <E3D/UI/Panel.h>
#include <E3D/UI/Panels.h>



EpIconDef3I(static, Sphere);
EpIconDef3I(static, UVSphere);
EpIconDef3I(static, GeoSphere);


static EPlugin*  _Plugin = NULL;

#define E3dFIBONACCI_ENABLE  1

enum
{
	E3dSphereUVSPHERE_POLY = 1,
	E3dSphereGEOSPHERE_POLY,
	E3dSphereUVEQUAL_AREA_SPHERE_POLY,
	E3dSphereUVNURBS,
	E3dSphereFIBONACCI
};

enum
{
	E3dSphereEAST = 1,
	E3dSphereWEST,
	E3dSphereNORTH,
	E3dSphereSOUTH,
};



typedef struct
{
	XeWidget  SmoothW, InsideOutW, TrianglesW, OriginAtBottomW, TangentsAlongMeridiansW,
            TypeUVShperePolygonsW, TypeGeoSpherePolygonsBW;

#if !ERELEASE_BUILD
	XeWidget  TypeUVEqualAreaSpherePolygonsBW;
#endif

#if E3dFIBONACCI_ENABLE
	XeWidget  TypeFibonacciPointsW;
#endif
	XeWidget  RadiusW, NorthMostLatitudeW, SouthMostLatitudeW, USectionsW, VSectionsW;
} E3dResourcesGUI;


static XeDWidget  _MenuButton = NULL, _ToolBarButton = NULL;


typedef struct
{
	int  Vertices[3];
	int  Edges[3];
} E3dSTriangle;

typedef struct E3dSEdge
{
	int  Start, End, Bisector, BisectorEdge0, BisectorEdge1;
} E3dSEdge;



#define E3dpICOSA_X  0.525731112119133606
#define E3dpICOSA_Z  0.850650808352039932

static const E3dCoordinate  _IcosaVertices[][3]=
{    
	{ -E3dpICOSA_X, 0.0, E3dpICOSA_Z }, {  E3dpICOSA_X, 0.0,  E3dpICOSA_Z }, { -E3dpICOSA_X, 0.0, -E3dpICOSA_Z }, { E3dpICOSA_X,  0.0, -E3dpICOSA_Z },    
	{  0.0, E3dpICOSA_Z, E3dpICOSA_X }, {  0.0, E3dpICOSA_Z, -E3dpICOSA_X }, {  0.0, -E3dpICOSA_Z, E3dpICOSA_X }, { 0.0, -E3dpICOSA_Z, -E3dpICOSA_X },    
	{  E3dpICOSA_Z, E3dpICOSA_X, 0.0 }, { -E3dpICOSA_Z,  E3dpICOSA_X, 0.0 }, {  E3dpICOSA_Z, -E3dpICOSA_X, 0.0 }, { -E3dpICOSA_Z, -E3dpICOSA_X, 0.0 } 
}; 

static const int  _IcosaPolyIndices[20][3]=
{
	{  1,4,0 }, {  4,9,0 }, {  4,5,9 }, {  8,5,4 }, { 1,8,4 }, { 1,10,8 }, { 10,3,8 }, {  8,3,5 }, { 3,2,5 }, { 3 ,7,2 },
	{ 3,10,7 }, { 10,6,7 }, { 6,11,7 }, { 6,0,11 }, { 6,1,0 }, { 10,1,6 }, { 11,0,9 }, { 2,11,9 }, { 5,2,9 }, { 11,2,7 }
};

static const int  _IcosaPolyEdgeIndices[20][3]=
{
	{  0, 1, 2 }, {  3, 4, 1 }, {  5, 6, 3 },  { 7, 5, 8 }, {  9, 8, 0 }, { 10, 11,  9 }, { 12, 13, 11 }, { 13, 14, 7 }, { 15,16,14 }, { 17,18,15 },
	{ 12,19,17 }, { 20,21,19 }, { 22,23,21 }, { 24,25,22 }, { 26, 2,24 }, { 10, 26, 20 }, { 25,  4, 27 }, { 28, 27,29 }, { 16,29, 6 }, { 28,18,23 }
};

static const int  _IcosaEdgeIndices[30][2]=
{
	{  1, 4 }, {  0, 4 }, {  0, 1 }, {  4, 9 }, {  0, 9 }, {  4, 5 }, {  5, 9 }, {  5, 8 }, {  4, 8 }, {  1, 8 },
	{  1,10 }, {  8,10 }, {  3,10 }, {  3, 8 }, {  3, 5 }, {  2, 3 }, {  2, 5 }, {  3, 7 }, {  2, 7 }, {  7,10 },
	{  6,10 }, {  6, 7 }, {  6,11 }, {  7,11 }, {  0, 6 }, {  0,11 }, {  1, 6 }, {  9,11 }, {  2,11 }, {  2, 9 }
};


typedef struct
{
	E3dObjectComponentCore;

// E3dSphereInfo part
//
	E3dCoordinate  Radius, NorthMostLatitude, SouthMostLatitude;
	E3dCoord2*     UnitCirclePoints;

	int            Iterations, USections, VSections, NVertices;
	int            Type;

	uint8_t        TangentDir;  // Direction of tangents (E3dSphereALONG_PARALLELS)
	EBool          Smooth, InsideOut, Triangles;
	char           OriginAt;    // E3dCENTER or E3dBOTTOM

// Internal variables
//
	EBool          CollapseNorthPole, CollapseSouthPole;
} E3dSphereInfo;


#define ROffset(field)  (void*)(EPtrOffset(E3dSphereInfo, field))

static EResource  _Resources[]=
{
	{ "Radius",             E3dCOORDINATE_TYPE,  ROffset(Radius) },
	{ "Iterations",         EResINT32,           ROffset(Iterations) },
	{ "USections",          EResINT32,           ROffset(USections) },
	{ "VSections",          EResINT32,           ROffset(VSections) },

	{ "NorthMostLatitude",  E3dCOORDINATE_TYPE,  ROffset(NorthMostLatitude) },
	{ "SouthMostLatitude",  E3dCOORDINATE_TYPE,  ROffset(SouthMostLatitude) },

	{ "Type",               EResINT32,           ROffset(Type) },
	{ "OriginAt",           EResINT8,            ROffset(OriginAt) },
	{ "TangentDir",         EResINT8,            ROffset(TangentDir) },
	{ "Smooth",             EResBOOLEAN,         ROffset(Smooth) },
	{ "InsideOut",          EResBOOLEAN,         ROffset(InsideOut) },
	{ "Triangles",          EResBOOLEAN,         ROffset(Triangles) },
};


static E3dObjectComponentClass*  _ComponentClass = NULL;


static void _UpdateUnitCirclePoints(E3dSphereInfo* PInfo)
{
	const int  LUSect = PInfo->USections;

	ENewN(LUSect, PInfo->UnitCirclePoints);
	int  LC = 0;E3dCoord2*  LUnitCirclePoint;LUnitCirclePoint = PInfo->UnitCirclePoints;for(;LC < LUSect;LC++, LUnitCirclePoint++)
	{
		const E3dCoordinate  LU = (E3dCoordinate)LC / (E3dCoordinate)LUSect;
		const E3dCoordinate  LPhi = E3dPI * 2.0 * LU - E3dPI * 0.5;    // -90 degrees, so the "seam" ("Dateline") is in the back (-Z)
		LUnitCirclePoint->X = cos(LPhi);LUnitCirclePoint->Y = -sin(LPhi);
	}
}


//================================================
// Compute Vertex coordinates for the Sphere
//================================================
static void _SetVertices(E3dMesh* PMesh, E3dSphereInfo* PInfo, E3dCoord3* PUnitSpherePoints)
{
	E3dVertex*           LVertices = PMesh->Vertices.Items;const E3dVertexSize  LVertexSize = PMesh->VertexSize;
	E3dCoord3*           LUnitSpherePoint;
	const E3dCoordinate  LRadius = PInfo->Radius;

	switch(PInfo->Type)
	{
		case E3dSphereUVSPHERE_POLY:
			{
				EIndex               LVStart, LVEnd;
				const EIndex         LUSect = PInfo->USections, LVSect = PInfo->VSections;
				E3dCoordinate        LLatitude = PInfo->NorthMostLatitude;
				const E3dCoordinate  LLatitudeStep = (PInfo->SouthMostLatitude - PInfo->NorthMostLatitude) / (E3dCoordinate)LVSect;

				if(PInfo->UnitCirclePoints == NULL) _UpdateUnitCirclePoints(PInfo);

				const E3dCoordinate  LYOffset = PInfo->OriginAt == E3dBOTTOM ? -E3d_Cos((PInfo->SouthMostLatitude - 90.0) * E3dDEGREES_TO_RADIANS) * LRadius: 0.0;

				E3dVertex*  LVertex = LVertices;
				if(PInfo->CollapseNorthPole)
				{
					LVertex->Position = E3dCoord3_s(0.0, LRadius + LYOffset, 0.0);EStruct_Inc(LVertex, LVertexSize);LVStart = 1;
					LLatitude += LLatitudeStep;
				}
				else LVStart = 0;

				if(PInfo->CollapseSouthPole) LVEnd = LVSect;
				else LVEnd = LVSect + 1;

				EIndex  LVC = LVStart;for(;LVC < LVEnd;LVC++, LLatitude += LLatitudeStep)
				{
					const E3dCoordinate	 LTheta  = (LLatitude - 90.0) * E3dDEGREES_TO_RADIANS;
					const E3dCoordinate	 LHeight = E3d_Cos(LTheta) * LRadius;
					const E3dCoordinate	 LDisc   = E3d_Sin(LTheta) * LRadius;

					const E3dCoord2*  LUnitCirclePoint = PInfo->UnitCirclePoints;
					EIndex  LUC = 0;for(;LUC < LUSect;LUC++, LUnitCirclePoint++)
					{
						LVertex->Position = E3dCoord3_s(LUnitCirclePoint->X * LDisc,  LHeight + LYOffset, LUnitCirclePoint->Y * LDisc);
						EStruct_Inc(LVertex, LVertexSize);
					}
				}

				if(PInfo->CollapseSouthPole)
				{
					LVertex->Position.X = 0.0;LVertex->Position.Y = -LRadius + LYOffset;LVertex->Position.Z = 0.0;EStruct_Inc(LVertex, LVertexSize);
				}
			}
		break;

		case E3dSphereUVEQUAL_AREA_SPHERE_POLY:
			{
				const EIndex         LUSect = PInfo->USections, LVSect = PInfo->VSections;
				const E3dCoordinate  LUStep = 1.0 / (E3dCoordinate)LUSect, LVStep = 1.0 / (E3dCoordinate)LVSect;

				EIndex  LVC = 0;E3dCoordinate  LV = 0.0;E3dVertex*  LVertex = LVertices;for(;LVC < LVSect;LVC++, LV += LVStep)
				{
					EIndex  LUC = 0;E3dCoordinate  LU = 0.0;for(;LUC < LUSect;LUC++, LU += LUStep)
					{
						E3dCoord3  LPosition;E3d_SampleSphere(LV, LU,  &LPosition);
						LVertex->Position = E3dCoord3_s(LPosition.Y * LRadius, -LPosition.Z * LRadius, -LPosition.X * LRadius);
						EStruct_Inc(LVertex, LVertexSize);
					}
				}
			}
		break;

		case E3dSphereGEOSPHERE_POLY:
			{
				EIndex  LVNum = 12;
				{
					EIndex  LC = 0, LPNum = 20;const EIndex  LIterations = PInfo->Iterations;for(;LC < LIterations;LC++)
					{
						LVNum += (LVNum + LPNum - 2);LPNum *= 4;
					}
				}
				E3dVertex*  LVertex = LVertices;LUnitSpherePoint = PUnitSpherePoints;
printf("%s() %s:%d  %sGREEN%s\n", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, ECodeNormal);fflush(stdout);
				if(LUnitSpherePoint)
				{
printf("%s() %s:%d  %sGREEN%s\n", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, ECodeNormal);fflush(stdout);
					const E3dCoordinate  LOffsetY = PInfo->OriginAt == E3dBOTTOM ? LRadius : 0.0;
					EIndex  LC = 0;for(;LC < LVNum;LC++, LUnitSpherePoint++, EStruct_Inc(LVertex, LVertexSize))
					{
						LVertex->Position = E3dCoord3_s(LUnitSpherePoint->X * LRadius, LUnitSpherePoint->Y * LRadius + LOffsetY, LUnitSpherePoint->Z * LRadius);
					}
				}
			}
		break;

		case E3dSphereFIBONACCI:
			{
				const EIndex  LVN = PInfo->NVertices;const E3dCoordinate  LVNF = (E3dCoordinate)LVN;
//printf("%s() %s:%d  %sFibonacci%s %lld\n", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, ECodeNormal, (long long)LVN);fflush(stdout);
/*
// This has a hole at the starting pole
				const float   LGoldenAngle = E3dPI * (3.0 - sqrtf(5.0));
//				const float   LGoldenAngle = E3dPI * (1.0 + sqrtf(5.0));

				const E3dCoordinate  LTStep = 1.0 / (E3dCoordinate)(LVN + 1);
				const E3dCoordinate  LZStep = ((1.0 / LVNF - 1.0) - (1.0 - 1.0 / LVNF)) / (LVNF + 1.0);

				EIndex  LVC = 0;float  LT = 0.0, LZ = 1.0 - 1.0 / LVNF;E3dVertex*  LVertex = LVertices;for(;LVC < LVN;LVC++, LT += 1.0, LZ += LZStep, EStruct_Inc(LVertex, LVertexSize))
				{
					const float  theta = LGoldenAngle * LT;
					const float  radius = sqrtf(1.0 - LZ * LZ);

					LVertex->Position =  E3dCoord3_s(E3d_Cos(theta) * radius * LRadius, E3d_Sin(theta) * radius * LRadius, LZ * LRadius);
				}
*/

				const double  LGoldenAngle = E3dPI * (1.0 + E3d_Sqrt(5.0));
				EIndex  LVC = 0;double  LIndexF = 0.5;E3dVertex*  LVertex = LVertices;for(;LVC < LVN;LVC++, LIndexF += 1.0, EStruct_Inc(LVertex, LVertexSize))
				{
					const double  LPhi = acos(1.0 - 2.0 * LIndexF / LVNF), LSinPhi = sin(LPhi);
					const double  LTheta = LGoldenAngle * LIndexF;

					LVertex->Position = E3dCoord3_s(cos(LTheta) * LSinPhi * LRadius, sin(LTheta) * LSinPhi * LRadius, cos(LPhi) * LRadius);
				}

			}
		break;
	}
}


/*
function createSphere (n) {
  var gN = 3 - PHI;
  var k = Math.floor(Math.log(n / 1.5) / Math.log(PHI));
  if (k % 2 === 1) {
    var j = Math.round((k + 7) * 0.5);
    gN = A[j] / A[j - 1];
  }
  return new Array(n).fill(0).map((d, i) => {
    var tu = (i + 0.5) / n;
    var tv = i / gN;
    var theta = Math.acos(2 * tu - 1.0) - Math.PI * 0.5;
    var phi = 2.0 * Math.PI * tv;
    return [
      Math.cos(theta) * Math.cos(phi),
      Math.sin(theta),
      Math.cos(theta) * Math.sin(phi),
    ];
  });
}
*/


#define _M_AddQuad(mV0, mV1, mV2, mV3)\
{\
	E3dPolygon_SetVa(LPolygon, LVertexNodeSize, 4,  mV0, mV1, mV2, mV3);\
	ENewN(2, LPolygon->ITriangles);\
	E3dITriangle*  lITriangle = LPolygon->ITriangles;\
	LPolygon->ITriangles_Count = 2;\
	lITriangle->VertexNodes[0] = 0;lITriangle->VertexNodes[1] = 1;lITriangle->VertexNodes[2] = 2;lITriangle++;\
	lITriangle->VertexNodes[0] = 2;lITriangle->VertexNodes[1] = 3;lITriangle->VertexNodes[2] = 0;\
	E3dVertexNode*  lVertexNode = LPolygon->VertexNodes;\
\
	E3dCoord2*     lUV;\
	lUV = (E3dCoord2*)EOffsetToPtr(lVertexNode, LUVOffsetInVtxNode);\
	LTangent = (E3dCoord3*)EOffsetToPtr(lVertexNode, LTangentOffsetInVtxNode);\
\
	lUV->X = LU;lUV->Y = LV + LVStep;EStruct_Inc(lUV, LVertexNodeSize);\
	{ const int  lIndex = LUC + (LVC + 2) * (PUSect + 1);*LTangent = PTangents[lIndex];EStruct_Inc(LTangent, LVertexNodeSize); }\
\
	lUV->X = LU + LUStep;lUV->Y = LV + LVStep;EStruct_Inc(lUV, LVertexNodeSize);\
	{ const int  lIndex = LUC + 1 + (LVC + 2) * (PUSect + 1);*LTangent = PTangents[lIndex];EStruct_Inc(LTangent, LVertexNodeSize); }\
\
	lUV->X = LU + LUStep;lUV->Y = LV;EStruct_Inc(lUV, LVertexNodeSize);\
	{ const int  lIndex = LUC + 1 + (LVC + 1) * (PUSect + 1);*LTangent = PTangents[lIndex];EStruct_Inc(LTangent, LVertexNodeSize); }\
\
	lUV->X = LU;lUV->Y = LV;\
	{ const int  lIndex = LUC + (LVC + 1) * (PUSect + 1);*LTangent = PTangents[lIndex]; }\
	LPolygon++;\
}


#define _M_AddQuadTris(mV0, mV1, mV2, mV3)\
{\
	LPolygon->ITriangles = NULL;\
	E3dPolygon_SetAsTriangle(LPolygon, LVertexNodeSize,  mV0, mV1, mV2);\
	E3dVertexNode*  lVertexNode = LPolygon->VertexNodes;\
	E3dCoord2*      lUV = (E3dCoord2*)EOffsetToPtr(lVertexNode, LUVOffsetInVtxNode);\
	LTangent = (E3dCoord3*)EOffsetToPtr(lVertexNode, LTangentOffsetInVtxNode);\
\
	lUV->X = LU;lUV->Y = LV + LVStep;EStruct_Inc(lUV, LVertexNodeSize);\
	{ const int  lIndex = LUC + (LVC + 2) * (PUSect + 1);*LTangent = PTangents[lIndex];EStruct_Inc(LTangent, LVertexNodeSize); }\
\
	lUV->X = LU + LUStep;lUV->Y = LV + LVStep;EStruct_Inc(lUV, LVertexNodeSize);\
	{ const int  lIndex = LUC + 1 + (LVC + 2) * (PUSect + 1);*LTangent = PTangents[lIndex];EStruct_Inc(LTangent, LVertexNodeSize); }\
\
	lUV->X = LU + LUStep;lUV->Y = LV;EStruct_Inc(lUV, LVertexNodeSize);\
	{ const int  lIndex = LUC + 1 + (LVC + 1) * (PUSect + 1);*LTangent = PTangents[lIndex];EStruct_Inc(LTangent, LVertexNodeSize); }\
	LPolygon++;\
\
\
	LPolygon->ITriangles = NULL;\
	E3dPolygon_SetAsTriangle(LPolygon, LVertexNodeSize,  mV2, mV3, mV0);\
	lVertexNode = LPolygon->VertexNodes;\
	lUV = (E3dCoord2*)EOffsetToPtr(lVertexNode, LUVOffsetInVtxNode);\
	LTangent = (E3dCoord3*)EOffsetToPtr(lVertexNode, LTangentOffsetInVtxNode);\
\
	lUV->X = LU + LUStep;lUV->Y = LV;EStruct_Inc(lUV, LVertexNodeSize);\
	{ const int  lIndex = LUC + 1 + (LVC + 1) * (PUSect + 1);*LTangent = PTangents[lIndex];EStruct_Inc(LTangent, LVertexNodeSize); }\
\
	lUV->X = LU;lUV->Y = LV;EStruct_Inc(lUV, LVertexNodeSize);\
	{ const int  lIndex = LUC + (LVC + 1) * (PUSect + 1);*LTangent = PTangents[lIndex];EStruct_Inc(LTangent, LVertexNodeSize); }\
\
	lUV->X = LU;lUV->Y = LV + LVStep;EStruct_Inc(lUV, LVertexNodeSize);\
	{ const int  lIndex = LUC + (LVC + 2) * (PUSect + 1);*LTangent = PTangents[lIndex]; }\
	LPolygon++;\
}


//================================================
// Compute Vertex coordinates for the Sphere
//================================================
static void _UVSphereCreatePolygons(E3dMesh* PMesh, E3dSphereInfo* PInfo, E3dCoord3* PTangents, const int PUSect, const int PVSect, const EIndex PVNum)
{
	E3dPolyGroup*  LPolyGroup = PMesh->PolyGroups.Items[0];

#define west(row, col)	(LOffset + (row) * PUSect + (col))
#define east(row, col)	(LOffset + (row) * PUSect + ((col) + 1) % PUSect)

	E3dPolygon*              LPolygon = LPolyGroup->Polygons.Items;

	E3dVertexNode*           LVertexNode;
	E3dCoord2*               LUV;
	E3dCoord3*               LTangent;
	E3dCoordinate            LUStep = 1.0 / (E3dCoordinate)PInfo->USections,
	                         LVStep = 1.0 / (E3dCoordinate)PInfo->VSections;
	const E3dVertexNodeSize  LVertexNodeSize = LPolyGroup->VertexNodeSize;


	const int  LUVOffsetInVtxNode = E3dPolyGroup_VertexAttributeOffset(LPolyGroup, E3dVtxAttribute_UV, 0);
	const int  LTangentOffsetInVtxNode = E3dPolyGroup_VertexAttributeOffset(LPolyGroup, E3dVtxAttribute_Tangent, 0);

	EIndex  LVMax = PVSect - 2;
	EIndex  LOffset;

	if(PInfo->CollapseNorthPole) LOffset = 1;
	else
	{
		LOffset = 0;LVMax++;
	}
	if(!PInfo->CollapseSouthPole) LVMax++;


	E3dCoordinate  LV;
	if(PInfo->CollapseNorthPole)
	{
		E3dCoordinate	LU = 0.0;

// Triangles around North pole
//
		LV = LVStep;
		EIndex  LUC = 0;for(;LUC < PUSect;LUC++, LU += LUStep)
		{
			E3dPolygon_SetAsTriangle(LPolygon, LVertexNodeSize, east(0, LUC), 0, west(0, LUC));

			LVertexNode = LPolygon->VertexNodes;
			LUV = (E3dCoord2*)EOffsetToPtr(LVertexNode, LUVOffsetInVtxNode);
			LTangent = (E3dCoord3*)EOffsetToPtr(LVertexNode, LTangentOffsetInVtxNode);

			LUV->X = LU + LUStep;LUV->Y = LV;EStruct_Inc(LUV, LVertexNodeSize);
			{ const int  LIndex = LUC + 1 + (PUSect + 1);*LTangent = PTangents[LIndex];EStruct_Inc(LTangent, LVertexNodeSize); }

			LUV->X = LU;LUV->Y = 0.0;EStruct_Inc(LUV, LVertexNodeSize);
			{ const int  LIndex = LUC;*LTangent = PTangents[LIndex];EStruct_Inc(LTangent, LVertexNodeSize); }

			LUV->X = LU;LUV->Y = LV;
			{ const int  LIndex = LUC + (PUSect + 1);*LTangent = PTangents[LIndex]; }

			LPolygon++;
		}
	}
	else LV = 0.0;


	if(PInfo->Triangles)
	{
		EIndex  LVC = 0;for(;LVC < LVMax;LVC++, LV += LVStep)
		{
			E3dCoordinate  LU = 0.0;

			EIndex  LUC = 0;for(;LUC < PUSect;LUC++, LU += LUStep)
			{
				_M_AddQuadTris(west(LVC + 1, LUC), east(LVC + 1, LUC), east(LVC, LUC), west(LVC, LUC));
			}
		}
	}
	else
	{
		EIndex  LVC = 0;for(;LVC < LVMax;LVC++, LV += LVStep)
		{
			E3dCoordinate  LU = 0.0;

			EIndex  LUC = 0;for(;LUC < PUSect;LUC++, LU += LUStep)
			{
				_M_AddQuad(west(LVC + 1, LUC), east(LVC + 1, LUC), east(LVC, LUC), west(LVC, LUC));
			}
		}
	}


	if(PInfo->CollapseSouthPole)
	{
		E3dCoordinate  LU = 0.0;

// Triangles around South pole
//
		EIndex  LUC = 0;for(;LUC < PUSect;LUC++, LU += LUStep)
		{
			E3dPolygon_SetAsTriangle(LPolygon, LVertexNodeSize, PVNum - 1, east(PVSect - 2, LUC), west(PVSect - 2, LUC));

			LVertexNode = LPolygon->VertexNodes;
			LUV = (E3dCoord2*)EOffsetToPtr(LVertexNode, LUVOffsetInVtxNode);
			LTangent = (E3dCoord3*)EOffsetToPtr(LVertexNode, LTangentOffsetInVtxNode);

			LUV->X = LU;LUV->Y = LV + LVStep;EStruct_Inc(LUV, LVertexNodeSize);
			{ const int  LIndex = LUC + (PUSect + 1) * PVSect;*LTangent = PTangents[LIndex];EStruct_Inc(LTangent, LVertexNodeSize); }

			LUV->X = LU + LUStep;LUV->Y = LV;EStruct_Inc(LUV, LVertexNodeSize);
			{ const int  LIndex = LUC+1 + (PUSect + 1) * (PVSect - 1);*LTangent = PTangents[LIndex];EStruct_Inc(LTangent, LVertexNodeSize); }

			LUV->X = LU;LUV->Y = LV;
			{ const int  LIndex = LUC + (PUSect + 1) * (PVSect - 1);*LTangent = PTangents[LIndex]; }

			LPolygon++;
		}
	}
}


//================================================
// Copy editable fields
//================================================
static void _CopyFields(const E3dSphereInfo* PSrc, E3dSphereInfo* PDst)
{
#define _CopyField(p)  PDst->p = PSrc->p
	_CopyField(Smooth);
	_CopyField(InsideOut);
	_CopyField(Triangles);
	_CopyField(Radius);
	_CopyField(Type);
	_CopyField(Iterations);
	_CopyField(USections);
	_CopyField(VSections);
	_CopyField(TangentDir);
#undef _CopyField
}


//========================================
// Update() (create/change) method
//========================================
static E3dObject* _Update1(E3dObject* PObject, E3dObjectComponent* PComponentTemplate,  EUpdateFlags* PTriggeredUpdateFlags, EChangeFlags* PTriggeredSceneChangeFlags,  const EBool PCheckOnly)
{
	E3dMesh*        LMesh;
	E3dPolyGroup*   LPolyGroup;
	E3dSphereInfo*  LInfo;
	int             LVNum = 0, LPNum = 0, LENum = 0;
	int             LUSect = 0, LVSect = 0, LIterations = 0;

	if(PObject == NULL)
	{
		E_New(E3dMesh, LMesh);
		PObject = (E3dObject*)LMesh;

		LInfo = (E3dSphereInfo*)E3dObjectComponent_Add((E3dObject*)LMesh, _ComponentClass);
		if(PComponentTemplate && PComponentTemplate->Class == _ComponentClass) _CopyFields((E3dSphereInfo*)PComponentTemplate, LInfo);
		LPolyGroup = E3dMesh_PolyGroupAdd(LMesh);if(LPolyGroup == NULL)
		{
			E_Delete(LMesh);return(NULL);
		}
	}
	else
	{
		if(!EObject_IsSubClass(PObject, E3dMesh)) return(NULL);

		LMesh = (E3dMesh*)PObject;
		LInfo = (E3dSphereInfo*)E3dObjectComponent_ByClass((E3dObject*)LMesh, _ComponentClass, FALSE);

		if(!PCheckOnly && LInfo->AttributeChangeCount == 0 && LInfo->InputChangeCount == 0) return(NULL);    // If no attributes or inputs changed, bail

		if(LInfo == NULL || LMesh->PolyGroups.Count == 0) return(NULL);
		LPolyGroup = LMesh->PolyGroups.Items[0];
	}

// CollapseNorthPole and CollapseSouthPole need to be updated, even if PCheckOnly is TRUE (e.g. after reading a file)
//
	switch(LInfo->Type)
	{
		case E3dSphereUVSPHERE_POLY:
			LUSect = LInfo->USections;
			LVSect = LInfo->VSections;

			if(LInfo->NorthMostLatitude == 90.0) LInfo->CollapseNorthPole = TRUE;
			else LInfo->CollapseNorthPole = FALSE;

			if(LInfo->SouthMostLatitude == -90.0) LInfo->CollapseSouthPole = TRUE;
			else LInfo->CollapseSouthPole = FALSE;
		break;
	}

	if(PCheckOnly) return(NULL);

//	const EBool          LSmooth = LInfo->Smooth;
//	const E3dCoordinate  LRadius = LInfo->Radius;
	switch(LInfo->Type)
	{
		case E3dSphereUVSPHERE_POLY:
			LUSect = LInfo->USections;
			LVSect = LInfo->VSections;

			LVNum = (LVSect - 1) * LUSect;

			if(LInfo->CollapseNorthPole) LVNum++;
			else LVNum += LUSect;

			if(LInfo->CollapseSouthPole) LVNum++;
			else LVNum += LUSect;

			if(LUSect < 3) LUSect = 3;
			if(LVSect < 2) LVSect = 2;

			if(LInfo->Triangles)
			{
				if(LInfo->CollapseNorthPole) LPNum = LUSect;
				else LPNum = LUSect * 2;

				LPNum += (LVSect - 2) * LUSect * 2;

				if(LInfo->CollapseSouthPole) LPNum += LUSect;
				else LPNum += LUSect * 2;
			}
			else
			{
				LPNum = LUSect * 2 + (LVSect - 2) * LUSect;
			}
		break;

		case E3dSphereUVEQUAL_AREA_SPHERE_POLY:
			LUSect = LInfo->USections;
			LVSect = LInfo->VSections;

			LVNum = LVSect * LUSect;
			LPNum = 0;
		break;

		case E3dSphereGEOSPHERE_POLY:
			{
				LIterations = LInfo->Iterations;
				LVNum = 12, LENum = 30, LPNum = 20;
				EIndex  LC = 0;for(;LC < LIterations;LC++)
				{
					LVNum += (LVNum + LPNum - 2);
					LENum = LENum * 2 + LPNum * 3;
					LPNum *= 4;
				}
			}
		break;

		case E3dSphereFIBONACCI:
			{
				LVNum = LInfo->NVertices;
			}
		break;
	}

	E3dMesh_FreeCaches(LMesh);

// Allocate Vertices for the Mesh
//
	if(LVNum != LMesh->Vertices.Count)
	{
		E3dMesh_VerticesFree(LMesh);
		E3dMesh_VerticesAdd(LMesh, LVNum);
	}

// Allocate Polygons for the Mesh
//
	if(LPolyGroup->Polygons.Items) E3dPolyGroup_FreePolygons(LPolyGroup);

	E3dPolygon*  LPolygons = NULL;if(LPNum > 0)
	{
		LPolygons = E3dPolygons_New(LPNum);if(LPolygons)
		{
			LPolyGroup->Polygons.Items = LPolygons;LPolyGroup->Polygons.Count = LPolyGroup->Polygons.CountAllocated = LPNum;
		}
	}

	if(LMesh->Vertices.Count > 0)
//	if(LVertices && LPolygons)
	{
//		int  LNumOfVertexNodes = 0;

		switch(LInfo->Type)
		{
			case E3dSphereUVSPHERE_POLY:
				{
					E3dCoord3*  LTangents;ENewN((LUSect + 1) * (LVSect + 1), LTangents);
					E3dCoord3*  LTangent = LTangents;

					if(LInfo->UnitCirclePoints == NULL) _UpdateUnitCirclePoints(LInfo);

					switch(LInfo->TangentDir)
					{
						case E3dSphereEAST:
						case E3dSphereWEST:
							{
								const E3dCoordinate  LY = LInfo->TangentDir == E3dSphereWEST ? -1.0 : 1.0;
								EIndex  LVC = 0;for(;LVC <= LVSect;LVC++)
								{
									const E3dCoord2*  LUnitCirclePoint = LInfo->UnitCirclePoints;

									EIndex  LUC = 0;for(;LUC <= LUSect;LUC++, LUnitCirclePoint++, LTangent++)
									{
										if(LUC == LUSect) LUnitCirclePoint = LInfo->UnitCirclePoints;

										E_Cross3_ssp(LUnitCirclePoint->X, 0.0, LUnitCirclePoint->Y,  0.0, LY, 0.0,  LTangent);
									}
								}
							}
						break;

						default:
							{
								const E3dCoordinate  LY = LInfo->TangentDir == E3dSphereNORTH ? -1.0 : 1.0;
								E3dCoordinate        LLatitude = LInfo->NorthMostLatitude;
								const E3dCoordinate  LLatitudeStep = (LInfo->SouthMostLatitude - LInfo->NorthMostLatitude) / (E3dCoordinate)LVSect;

								EIndex  LVC = 0;for(;LVC <= LVSect;LVC++, LLatitude += LLatitudeStep)
								{
									const E3dCoordinate  LTheta = (LLatitude - 90.0) * E3dDEGREES_TO_RADIANS;
									const E3dCoordinate  LHeight = cos(LTheta);
									const E3dCoordinate  LDisc = sin(LTheta);
									const E3dCoord2*     LUnitCirclePoint = LInfo->UnitCirclePoints;

									EIndex  LUC = 0;for(;LUC <= LUSect;LUC++, LUnitCirclePoint++, LTangent++)
									{
										if(LUC == LUSect) LUnitCirclePoint = LInfo->UnitCirclePoints;

// Along-parallels tangent...
//
										E3dCoord3  LT;E_Cross3_ssv(LUnitCirclePoint->X, 0.0, LUnitCirclePoint->Y,  0.0, LY, 0.0,  LT);

// ..cross product with the normal will give along-meridian tangents
//
										E_Cross3_svp(LT,  LUnitCirclePoint->X * LDisc, LHeight, LUnitCirclePoint->Y * LDisc,  LTangent);
									}
								}
							}
						break;
					}

// Make sure the VertexNodes have Normals, UVs and Tangents
//
					E3dPolyGroup_VertexAttributeAdd(LPolyGroup,  NULL, E3dVtxAttribute_Normal, 0,  NULL);
					E3dPolyGroup_VertexAttributeAdd(LPolyGroup,  NULL, E3dVtxAttribute_UV, 0,  NULL);
					E3dPolyGroup_VertexAttributeAdd(LPolyGroup,  NULL, E3dVtxAttribute_Tangent, 0,  NULL);

//					LNumOfVertexNodes = LUSect * 3 * 2 + (LVSect - 2) * LUSect * 4;

					_SetVertices(LMesh, LInfo, NULL);
					_UVSphereCreatePolygons(LMesh, LInfo, LTangents, LUSect, LVSect, LVNum);
//					LPolyGroup->Polygons_Count = 0;

					EFree(LTangents);
				}
			break;

			case E3dSphereUVEQUAL_AREA_SPHERE_POLY:
				{
printf("%s() %s:%d\n", __FUNCTION__, __FILE__, __LINE__);fflush(stdout);
					_SetVertices(LMesh, LInfo, NULL);
					if(LMesh->PolyGroups.Count > 0) E3dPolyGroup_FreePolygons(LMesh->PolyGroups.Items[0]);
				}
			break;

			case E3dSphereGEOSPHERE_POLY:
				{
					E3dCoord3*     LUnitSpherePoints;ENewN(LVNum, LUnitSpherePoints);

					E3dPolyGroup_VertexAttributeDel(LPolyGroup,  E3dVtxAttribute_UV, 0);

					E3dSTriangle*  LTrianglesA;ENewN(LPNum, LTrianglesA);
					E3dSTriangle*  LTrianglesB;ENewN(LPNum, LTrianglesB);

					E3dSEdge*      LEdgesA;ENewN(LENum, LEdgesA);
					E3dSEdge*      LEdgesB;ENewN(LENum, LEdgesB);

// Store original icosahedron vertices
//
					if(LTrianglesA && LTrianglesB && LEdgesA && LEdgesB)
					{
						E3dCoord3*  LUnitSpherePoint = LUnitSpherePoints;

						EIndex  LH = 0;

						{
							for(;LH < 12;LH++, LUnitSpherePoint++)
							{
								*LUnitSpherePoint = E3dCoord3_s(_IcosaVertices[LH][0], _IcosaVertices[LH][1], _IcosaVertices[LH][2]);
							}
						}

						E3dSTriangle*  LTrianglesS = LTrianglesA;E3dSTriangle*  LTrianglesD = LTrianglesB;E3dSEdge*  LEdgesS = LEdgesA;E3dSEdge*  LEdgesD = LEdgesB;

// Copy icosa triangles to the current triangle list
//
						{
							EIndex  LC = 0;for(;LC < 20;LC++)
							{
								LTrianglesS[LC].Vertices[0] = _IcosaPolyIndices[LC][0];
								LTrianglesS[LC].Vertices[1] = _IcosaPolyIndices[LC][1];
								LTrianglesS[LC].Vertices[2] = _IcosaPolyIndices[LC][2];
								LTrianglesS[LC].Edges[0] = _IcosaPolyEdgeIndices[LC][0];
								LTrianglesS[LC].Edges[1] = _IcosaPolyEdgeIndices[LC][1];
								LTrianglesS[LC].Edges[2] = _IcosaPolyEdgeIndices[LC][2];
							}
						}

// Copy icosa edges to the current edge list
//
						{
							EIndex  LC = 0;for(;LC < 30;LC++)
							{
								LEdgesS[LC].Start = _IcosaEdgeIndices[LC][0];
								LEdgesS[LC].End = _IcosaEdgeIndices[LC][1];
							}
						}

						EIndex  LCIter = 0, LNEdg = 30, LNTri = 20;for(;LCIter < LIterations;LCIter++, LNTri *= 4)
						{
// Compute and normalize bi-sector points of the edges and add bi-sector edges to the new edge list
//
							EIndex  LCED = 0;

							{
								EIndex  LC = 0;for(;LC < LNEdg;LC++)
								{
									const E3dCoord3*  LPoint0 = LUnitSpherePoints + LEdgesS[LC].Start;
									const E3dCoord3*  LPoint1 = LUnitSpherePoints + LEdgesS[LC].End;
									LEdgesS[LC].Bisector = LH;

									const E3dCoordinate  LX = LPoint0->X + LPoint1->X, LY = LPoint0->Y + LPoint1->Y, LZ = LPoint0->Z + LPoint1->Z;

									E3dCoordinate  LLenRec = E3d_Sqrt(LX * LX + LY * LY + LZ * LZ);if(LLenRec > 0.0) LLenRec = 1.0 / LLenRec;

									LUnitSpherePoints[LH] = E3dCoord3_s(LX * LLenRec, LY * LLenRec, LZ * LLenRec);

									LEdgesS[LC].BisectorEdge0 = LCED;
									LEdgesD[LCED].Start = LEdgesS[LC].Start;LEdgesD[LCED].End = LH;LCED++;
									LEdgesS[LC].BisectorEdge1 = LCED;
									LEdgesD[LCED].Start = LEdgesS[LC].End;LEdgesD[LCED].End = LH;LCED++;
									LH++;
								}
							}

// Subdivide triangles
//
							EIndex  LC = 0;E3dSTriangle*  LTriangleS = LTrianglesS;E3dSTriangle*  LTriangleD = LTrianglesD;for(;LC < LNTri;LC++, LTriangleS++)
							{
								int*  LVerticesTS = LTriangleS->Vertices;
								int*  LEdgesTS = LTriangleS->Edges;
								LTriangleD->Vertices[0] = LVerticesTS[0];
								LTriangleD->Vertices[1] = LEdgesS[LEdgesTS[0]].Bisector;
								LTriangleD->Vertices[2] = LEdgesS[LEdgesTS[2]].Bisector;
								if(LEdgesD[LTriangleD->Edges[0] = LEdgesS[LEdgesTS[0]].BisectorEdge0].Start != LVerticesTS[0]) LTriangleD->Edges[0] += 1;
								LTriangleD->Edges[1] = LCED;

								if(LEdgesD[LTriangleD->Edges[2] = LEdgesS[LEdgesTS[2]].BisectorEdge0].Start != LVerticesTS[0]) LTriangleD->Edges[2] += 1;
								LEdgesD[LCED].Start = LEdgesS[LEdgesTS[0]].Bisector;LEdgesD[LCED].End = LEdgesS[LEdgesTS[2]].Bisector;LCED++;
								LTriangleD++;

								LTriangleD->Vertices[0] = LVerticesTS[1];
								LTriangleD->Vertices[1] = LEdgesS[LEdgesTS[1]].Bisector;
								LTriangleD->Vertices[2] = LEdgesS[LEdgesTS[0]].Bisector;

								if(LEdgesD[LTriangleD->Edges[0] = LEdgesS[LEdgesTS[1]].BisectorEdge0].Start != LVerticesTS[1]) LTriangleD->Edges[0] += 1;
								LTriangleD->Edges[1] = LCED;

								if(LEdgesD[LTriangleD->Edges[2] = LEdgesS[LEdgesTS[0]].BisectorEdge0].Start != LVerticesTS[1]) LTriangleD->Edges[2] += 1;
								LEdgesD[LCED].Start = LEdgesS[LEdgesTS[1]].Bisector;LEdgesD[LCED].End = LEdgesS[LEdgesTS[0]].Bisector;LCED++;
								LTriangleD++;

								LTriangleD->Vertices[0] = LVerticesTS[2];
								LTriangleD->Vertices[1] = LEdgesS[LEdgesTS[2]].Bisector;
								LTriangleD->Vertices[2] = LEdgesS[LEdgesTS[1]].Bisector;
								if(LEdgesD[LTriangleD->Edges[0] = LEdgesS[LEdgesTS[2]].BisectorEdge0].Start != LVerticesTS[2]) LTriangleD->Edges[0] += 1;
								LTriangleD->Edges[1] = LCED;
								if(LEdgesD[LTriangleD->Edges[2] = LEdgesS[LEdgesTS[1]].BisectorEdge0].Start != LVerticesTS[2]) LTriangleD->Edges[2] += 1;
								LEdgesD[LCED].Start = LEdgesS[LEdgesTS[2]].Bisector;LEdgesD[LCED].End = LEdgesS[LEdgesTS[1]].Bisector;LCED++;
								LTriangleD++;

								LTriangleD->Vertices[1] = LEdgesS[LEdgesTS[1]].Bisector;
								LTriangleD->Vertices[2] = LEdgesS[LEdgesTS[2]].Bisector;
								LTriangleD->Vertices[0] = LEdgesS[LEdgesTS[0]].Bisector;
								LTriangleD->Edges[2] = LCED - 3;
								LTriangleD->Edges[0] = LCED - 2;
								LTriangleD->Edges[1] = LCED - 1;
								LTriangleD++;
							}
							LNEdg = LCED;

// Switch source and destination buffers
//
							if(LTrianglesS == LTrianglesA)
							{
								LTrianglesS = LTrianglesB;LTrianglesD = LTrianglesA;LEdgesS = LEdgesB;LEdgesD = LEdgesA;
							}
							else
							{
								LTrianglesS = LTrianglesA;LTrianglesD = LTrianglesB;LEdgesS = LEdgesA;LEdgesD = LEdgesB;
							}
						}
						EFreeChkClr(LEdgesA);
						EFreeChkClr(LEdgesB);

						_SetVertices(LMesh, LInfo, LUnitSpherePoints);
						EFree(LUnitSpherePoints);


						{
							const EIndex  LVN = LMesh->Vertices.Count;
							E3dCoord3*    LTangents;ENewN(LVN, LTangents);

							{
								E3dCoord3*  LTangent = LTangents;
								E3dVertex*  LVertex = LMesh->Vertices.Items;const E3dVertexSize  LVertexSize = LMesh->VertexSize;

								switch(LInfo->TangentDir)
								{
									case E3dSphereEAST:
									case E3dSphereWEST:
										{
											const E3dCoordinate  LY = LInfo->TangentDir == E3dSphereWEST ? -1.0 : 1.0;
											EIndex  LVC = 0;for(;LVC < LVN;LVC++, EStruct_Inc(LVertex, LVertexSize), LTangent++)
											{
												E_Cross3_ssp(LVertex->Position.X, 0.0, LVertex->Position.Z,  0.0, LY, 0.0,  LTangent);
												E3d_Normalize3_p(LTangent);
											}
										}
									break;

									default:
										{
											const E3dCoordinate  LY = LInfo->TangentDir == E3dSphereNORTH ? -1.0 : 1.0;
											EIndex  LVC = 0;for(;LVC < LVN;LVC++, EStruct_Inc(LVertex, LVertexSize), LTangent++)
											{
// Along-parallels tangent...
//
												E3dCoord3  LT;E_Cross3_ssv(LVertex->Position.X, 0.0, LVertex->Position.Z,  0.0, LY, 0.0,  LT);

// ..cross product with the normal will give along-meridian tangents
//
												E_Cross3_vvp(LT,  LVertex->Position,  LTangent);
												E3d_Normalize3_p(LTangent);
											}
										}
									break;
								}
							}

// Make sure the VertexNodes have UVs and Tangents
//
							{
//								E3dPolyGroup_VertexAttributeAdd(LPolyGroup,  NULL, E3dVtxAttribute_UV, 0,  NULL);
								E3dPolyGroup_VertexAttributeAdd(LPolyGroup,  NULL, E3dVtxAttribute_Tangent, 0,  NULL);
								const int                LTangentOffsetInVtxNode = E3dPolyGroup_VertexAttributeOffset(LPolyGroup, E3dVtxAttribute_Tangent, 0);
								const E3dVertexNodeSize  LVertexNodeSize = LPolyGroup->VertexNodeSize;

								E3dPolygon*    LPolygon = LPolygons;
								E3dSTriangle*  LSTriangle = LTrianglesS;

								EIndex  LC = 0;for(;LC < LNTri;LC++, LSTriangle++)
								{
									const int*  LVertexIndices = LSTriangle->Vertices;
									E3dPolygon_SetAsTriangle(LPolygon, LVertexNodeSize, LVertexIndices[0], LVertexIndices[1], LVertexIndices[2]);

									E3dVertexNode*  LVertexNode = LPolygon->VertexNodes;
									E3dCoord3*      LTangent = (E3dCoord3*)EOffsetToPtr(LVertexNode, LTangentOffsetInVtxNode);

									*LTangent = LTangents[LVertexIndices[0]];EStruct_Inc(LTangent, LVertexNodeSize);
									*LTangent = LTangents[LVertexIndices[1]];EStruct_Inc(LTangent, LVertexNodeSize);
									*LTangent = LTangents[LVertexIndices[2]];

									LPolygon++;
								}
							}

							EFree(LTangents);
						}
					}

					EFreeChkClr(LTrianglesA);
					EFreeChkClr(LTrianglesB);
					EFreeChkClr(LEdgesA);
					EFreeChkClr(LEdgesB);
				}
			break;

			case E3dSphereFIBONACCI:
				{
					_SetVertices(LMesh, LInfo, NULL);
printf("%s() %s:%d  %sFibonacci. No Polys for now.%s\n", __FUNCTION__, __FILE__, __LINE__, ECodeYellow, ECodeNormal);fflush(stdout);
				}
			break;
		}

		if(LInfo->InsideOut)
		{
			const EIndex             LPN = LPolyGroup->Polygons.Count;
			const E3dVertexNodeSize  LVertexNodeSize = LPolyGroup->VertexNodeSize;
			const int                LNormalOffset = E3dVertexNodeAttribute_Offset(&LPolyGroup->VertexAttributes,  E3dVtxAttribute_Normal, 0);
			E3dPolygon*              LPolygon = LPolygons;

//printf("%s()-01 LInfo->InsideOut %02x %d\n", __FUNCTION__, LInfo->InsideOut, LPN);fflush(stdout);
			EIndex  LC = 0;for(;LC < LPN;LC++, LPolygon++) E3dPolygon_Invert(LPolygon, LVertexNodeSize, LNormalOffset);
		}

		if(LInfo->Smooth)
		{
			LPolyGroup->VertexNormalType = E3dNormalFROM_SMOOTHING_ANGLE;
			LPolyGroup->Flags |= E3dUSE_VERTEX_NORMALS;
		}
		else
		{
			LPolyGroup->VertexNormalType = 0;
			LPolyGroup->Flags &= ~E3dUSE_VERTEX_NORMALS;
		}

		if(PTriggeredUpdateFlags) *PTriggeredUpdateFlags = E3dUpdateALL;
		LInfo->AttributeChangeCount = LInfo->InputChangeCount = 0;
	}

	return(PObject);
}


//========================================
// Set GUI for sphere type
//========================================
static void _SetGUIToType(E3dSphereInfo* PInfo, E3dResourcesGUI* PResourcesGUI)
{
	XeString        LXeStr;
	XeWidget        LIndentWs[7];int  LIndentN;      // For indenting the ScaleWidgets
	XeWidget        LTraverseWs[7];int  LTraverseN;  // For TextField traversal
	XeChangeManage  LWidgetsToManageUnmanage[8];XeChangeManage*  LChild = LWidgetsToManageUnmanage;

	switch(PInfo->Type)
	{
		case E3dSphereUVNURBS:
		case E3dSphereUVSPHERE_POLY:
			XeScale_SetValueAndRange(PResourcesGUI->USectionsW,  PInfo->USections,  3, 720,  FALSE);
			XeScale_SetValueAndRange(PResourcesGUI->VSectionsW,  PInfo->VSections,  2, 720,  FALSE);
			LXeStr = XeString_SimpleNew("U sections");if(LXeStr)
			{
				Xe_SetValuesVa(PResourcesGUI->USectionsW, XeNLabelString, LXeStr, NULL);
				XeString_Free(LXeStr);
			}

			LChild->W = PResourcesGUI->VSectionsW;        LChild->Manage = TRUE;LChild++;
			LChild->W = PResourcesGUI->NorthMostLatitudeW;LChild->Manage = TRUE;LChild++;
			LChild->W = PResourcesGUI->SouthMostLatitudeW;LChild->Manage = TRUE;LChild++;

			XeWidget_Manage(PResourcesGUI->TrianglesW);

//			XeScale_SetValue_Coord(PResourcesGUI->NorthMostLatitudeW, PInfo->NorthMostLatitude, FALSE);
//			XeScale_SetValue_Coord(PResourcesGUI->SouthMostLatitudeW, PInfo->SouthMostLatitude, FALSE);

			LIndentN = LTraverseN = 0;
			LIndentWs[LIndentN++] = LTraverseWs[LTraverseN++] = PResourcesGUI->RadiusW;
			LIndentWs[LIndentN++] = LTraverseWs[LTraverseN++] = PResourcesGUI->NorthMostLatitudeW;
			LIndentWs[LIndentN++] = LTraverseWs[LTraverseN++] = PResourcesGUI->SouthMostLatitudeW;
			LIndentWs[LIndentN++] = LTraverseWs[LTraverseN++] = PResourcesGUI->USectionsW;
			LIndentWs[LIndentN++] = LTraverseWs[LTraverseN++] = PResourcesGUI->VSectionsW;
			XeWidgets_IndentFromLeft(LIndentWs, LIndentN, 4);
		break;

		case E3dSphereUVEQUAL_AREA_SPHERE_POLY:
			XeScale_SetValueAndRange(PResourcesGUI->USectionsW,  PInfo->USections,  3, 720,  FALSE);
			XeScale_SetValueAndRange(PResourcesGUI->VSectionsW,  PInfo->VSections,  2, 720,  FALSE);
			LChild->W = PResourcesGUI->NorthMostLatitudeW;LChild->Manage = FALSE;LChild++;
			LChild->W = PResourcesGUI->SouthMostLatitudeW;LChild->Manage = FALSE;LChild++;
		break;

		case E3dSphereGEOSPHERE_POLY:
			XeScale_SetValueAndRange(PResourcesGUI->USectionsW,  PInfo->Iterations,  0, 7,  FALSE);
			LXeStr = XeString_SimpleNew("Iterations");if(LXeStr)
			{
				Xe_SetValuesVa(PResourcesGUI->USectionsW, XeNLabelString, LXeStr, NULL);
				XeString_Free(LXeStr);
			}
//			XeScale_SetValue(PResourcesGUI->USectionsW, PInfo->Iterations, FALSE);

			LChild->W = PResourcesGUI->VSectionsW;        LChild->Manage = FALSE;LChild++;
			LChild->W = PResourcesGUI->NorthMostLatitudeW;LChild->Manage = FALSE;LChild++;
			LChild->W = PResourcesGUI->SouthMostLatitudeW;LChild->Manage = FALSE;LChild++;

//			XeWidgets_ChangeManage(LWidgetsToManageUnmanage, LChild - LWidgetsToManageUnmanage);

			XeWidget_Unmanage(PResourcesGUI->TrianglesW);

			LIndentN = LTraverseN = 0;
			LIndentWs[LIndentN++] = LTraverseWs[LTraverseN++] = PResourcesGUI->RadiusW;
			LIndentWs[LIndentN++] = LTraverseWs[LTraverseN++] = PResourcesGUI->USectionsW;
			XeWidgets_IndentFromLeft(LIndentWs, LIndentN, 4);
		break;

		case E3dSphereFIBONACCI:
			XeScale_SetValueAndRange(PResourcesGUI->USectionsW,  PInfo->NVertices,  5, 250000,  FALSE);
			LXeStr = XeString_SimpleNew("Vertices");if(LXeStr)
			{
				Xe_SetValuesVa(PResourcesGUI->USectionsW, XeNLabelString, LXeStr, NULL);
				XeString_Free(LXeStr);
			}
//			XeScale_SetValue(PResourcesGUI->USectionsW, PInfo->Iterations, FALSE);

			LChild->W = PResourcesGUI->VSectionsW;        LChild->Manage = FALSE;LChild++;
			LChild->W = PResourcesGUI->NorthMostLatitudeW;LChild->Manage = FALSE;LChild++;
			LChild->W = PResourcesGUI->SouthMostLatitudeW;LChild->Manage = FALSE;LChild++;

//			XeWidgets_ChangeManage(LWidgetsToManageUnmanage, LChild - LWidgetsToManageUnmanage);

			XeWidget_Unmanage(PResourcesGUI->TrianglesW);

			LIndentN = LTraverseN = 0;
			LIndentWs[LIndentN++] = LTraverseWs[LTraverseN++] = PResourcesGUI->RadiusW;
			LIndentWs[LIndentN++] = LTraverseWs[LTraverseN++] = PResourcesGUI->USectionsW;
			XeWidgets_IndentFromLeft(LIndentWs, LIndentN, 4);
		break;
	}

	if(LChild > LWidgetsToManageUnmanage) XeWidgets_ChangeManage(LWidgetsToManageUnmanage, LChild - LWidgetsToManageUnmanage);
}


//========================================
// Attributes GUI callback handler
//========================================
static void _DCB(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dScene*                LScene = E3d_Scene;
	E3dObjectPanel*          LPanel = (E3dObjectPanel*)PClientData;
	E3dObject*               LObject = LPanel->Object;
	E3dSphereInfo*           LInfo = (E3dSphereInfo*)E3dObjectComponent_ByClass(LObject, _ComponentClass, FALSE);
	XeComponentClassDrawer*  LDrawer = XeInfoPanel_GetFolderByClass((XeInfoPanel*)LPanel, _ComponentClass);
	E3dResourcesGUI*         LGUI = (E3dResourcesGUI*)LDrawer->ResourcesGUI;
	EChangeFlags             LChanged = 0;

	if(PW == LGUI->TypeUVShperePolygonsW)
	{
		if(LInfo->Type != E3dSphereUVSPHERE_POLY)
		{
			EFreeChkClr(LInfo->UnitCirclePoints);
			LInfo->Type = E3dSphereUVSPHERE_POLY;
			LInfo->AttributeChangeCount++;E3dObject_CustomUpdate(LObject, LInfo,  E3dCallALL,  &LChanged);
		}
	}
#if !ERELEASE_BUILD
	else if(PW == LGUI->TypeUVEqualAreaSpherePolygonsBW)
	{
		if(LInfo->Type != E3dSphereUVEQUAL_AREA_SPHERE_POLY)
		{
			EFreeChkClr(LInfo->UnitCirclePoints);
			LInfo->Type = E3dSphereUVEQUAL_AREA_SPHERE_POLY;
			LInfo->AttributeChangeCount++;E3dObject_CustomUpdate(LObject, LInfo,  E3dCallALL,  &LChanged);
		}
	}
#endif
	else if(PW == LGUI->TypeGeoSpherePolygonsBW)
	{
		if(LInfo->Type != E3dSphereGEOSPHERE_POLY)
		{
			EFreeChkClr(LInfo->UnitCirclePoints);
			LInfo->Type = E3dSphereGEOSPHERE_POLY;
			LInfo->AttributeChangeCount++;E3dObject_CustomUpdate(LObject, LInfo,  E3dCallALL,  &LChanged);
		}
	}
#if E3dFIBONACCI_ENABLE
	else if(PW == LGUI->TypeFibonacciPointsW)
	{
		if(LInfo->Type != E3dSphereFIBONACCI)
		{
			EFreeChkClr(LInfo->UnitCirclePoints);
			LInfo->Type = E3dSphereFIBONACCI;
			LInfo->AttributeChangeCount++;E3dObject_CustomUpdate(LObject, LInfo,  E3dCallALL,  &LChanged);
		}
	}
#endif
	else if(PW == LGUI->TangentsAlongMeridiansW)
	{
		if(((XeToggleButtonCallbackStruct*)PCallData)->Set) LInfo->TangentDir = E3dSphereSOUTH;
		else LInfo->TangentDir = E3dSphereWEST;

		LInfo->AttributeChangeCount++;E3dObject_CustomUpdate(LObject, LInfo,  E3dCallALL,  &LChanged);
	}
	else if(PW == LGUI->RadiusW)
	{
		LInfo->Radius = ((XeScaleCallbackStruct*)PCallData)->Value.m_Coordinate;LInfo->AttributeChangeCount++;

		switch(LInfo->Type)
		{
			case E3dSphereUVSPHERE_POLY:
			case E3dSphereUVEQUAL_AREA_SPHERE_POLY:
			case E3dSphereFIBONACCI:
				{
					E3dMesh*  LMesh = (E3dMesh*)LObject;

					_SetVertices(LMesh, LInfo, NULL);
					LChanged |= E3dObject_UpdateForDisplay(LObject,  E3dUpdateMeshSHAPE|E3dUpdateTextureMAPPING, E3dCallALL,  LScene);
				}
			break;

			case E3dSphereGEOSPHERE_POLY:
				LInfo->AttributeChangeCount++;
				E3dObject_CustomUpdate(LObject, LInfo,  E3dCallALL,  &LChanged);
			break;
		}
	}
	else if(PW == LGUI->USectionsW || PW == LGUI->VSectionsW)
	{
		EFreeChkClr(LInfo->UnitCirclePoints);

		switch(LInfo->Type)
		{
			case E3dSphereUVNURBS:
			case E3dSphereUVSPHERE_POLY:
			case E3dSphereUVEQUAL_AREA_SPHERE_POLY:
				LInfo->USections = XeScale_GetValue(LGUI->USectionsW);
				LInfo->VSections = XeScale_GetValue(LGUI->VSectionsW);
				LInfo->AttributeChangeCount++;
			break;

			case E3dSphereGEOSPHERE_POLY:
				LInfo->Iterations = XeScale_GetValue(LGUI->USectionsW);
				LInfo->AttributeChangeCount++;
			break;

			case E3dSphereFIBONACCI:
				LInfo->NVertices = XeScale_GetValue(LGUI->USectionsW);
				LInfo->AttributeChangeCount++;
			break;
		}

		E3dObject_CustomUpdate(LObject, LInfo,  E3dCallALL,  &LChanged);
	}
	else if(PW == LGUI->NorthMostLatitudeW)
	{
		LInfo->NorthMostLatitude = ((XeScaleCallbackStruct*)PCallData)->Value.m_Coordinate;
		if(LInfo->NorthMostLatitude < LInfo->SouthMostLatitude)
		{
			LInfo->SouthMostLatitude = LInfo->NorthMostLatitude;
			XeScale_SetValue_Coord(LGUI->SouthMostLatitudeW, LInfo->SouthMostLatitude, FALSE);
		}

		LInfo->AttributeChangeCount++;E3dObject_CustomUpdate(LObject, LInfo,  E3dCallALL,  &LChanged);
	}
	else if(PW == LGUI->SouthMostLatitudeW)
	{
		LInfo->SouthMostLatitude = ((XeScaleCallbackStruct*)PCallData)->Value.m_Coordinate;
		if(LInfo->SouthMostLatitude > LInfo->NorthMostLatitude)
		{
			LInfo->NorthMostLatitude = LInfo->SouthMostLatitude;
			XeScale_SetValue_Coord(LGUI->NorthMostLatitudeW, LInfo->NorthMostLatitude, FALSE);
		}

		LInfo->AttributeChangeCount++;E3dObject_CustomUpdate(LObject, LInfo,  E3dCallALL,  &LChanged);
	}
	else if(PW == LGUI->InsideOutW)
	{
		LInfo->InsideOut = (EBool)((XeToggleButtonCallbackStruct*)PCallData)->Set;LInfo->AttributeChangeCount++;

		E3dObject_CustomUpdate(LPanel->Object, LInfo,  E3dCallALL,  &LChanged);
	}
	else if(PW == LGUI->SmoothW)
	{
		switch(LInfo->Type)
		{
			case E3dSphereUVSPHERE_POLY:
			case E3dSphereUVEQUAL_AREA_SPHERE_POLY:
			case E3dSphereGEOSPHERE_POLY:
				{
					E3dMesh*       LMesh = (E3dMesh*)LObject;
					E3dPolyGroup*  LPolyGroup = LMesh->PolyGroups.Items[0];

					LInfo->Smooth = (EBool)(((XeToggleButtonCallbackStruct*)PCallData)->Set);if(LInfo->Smooth)
					{
						LPolyGroup->VertexNormalType = E3dNormalFROM_SMOOTHING_ANGLE;
						LPolyGroup->Flags |= E3dUSE_VERTEX_NORMALS;
					}
					else
					{
						LPolyGroup->VertexNormalType = 0;
						LPolyGroup->Flags &= ~E3dUSE_VERTEX_NORMALS;
					}
				}
			break;
		}

		LChanged |= E3dObject_UpdateForDisplay(LObject,  E3dUpdateVERTEX_NORMAL, E3dCallALL,  LScene);
	}
	else if(PW == LGUI->TrianglesW)
	{
		LInfo->Triangles = (EBool)((XeToggleButtonCallbackStruct*)PCallData)->Set;

		LInfo->AttributeChangeCount++;E3dObject_CustomUpdate(LPanel->Object, LInfo,  E3dCallALL,  &LChanged);
	}
	else if(PW == LGUI->OriginAtBottomW)
	{
		if(((XeToggleButtonCallbackStruct*)PCallData)->Set) LInfo->OriginAt = E3dBOTTOM;
		else LInfo->OriginAt = E3dCENTER;
		LInfo->AttributeChangeCount++;

		E3dMesh*  LMesh = (E3dMesh*)LObject;

		switch(LInfo->Type)
		{
			case E3dSphereUVNURBS:
			case E3dSphereUVSPHERE_POLY:
			case E3dSphereUVEQUAL_AREA_SPHERE_POLY:
			case E3dSphereFIBONACCI:
				_SetVertices(LMesh, LInfo, NULL);
				LChanged |= E3dObject_UpdateForDisplay(LObject,  E3dUpdateMeshSHAPE|E3dUpdateTextureMAPPING, E3dCallALL,  LScene);
			break;

			case E3dSphereGEOSPHERE_POLY:
				LInfo->AttributeChangeCount++;
				E3dObject_CustomUpdate(LObject, LInfo,  E3dCallALL,  &LChanged);
			break;
		}
	}

	E3dWindows_RedrawByChangeFlags(LScene, LChanged);
}


//========================================
// GUI
//========================================
static void _GUI(E3dObject* PObject, E3dObjectComponent* PInfo, void* PPanel, void* PDrawer, const int PAction)
{
	E3dObjectPanel*          LPanel = (E3dObjectPanel*)PPanel;
	XeComponentClassDrawer*  LDrawer = (XeComponentClassDrawer*)PDrawer;
	E3dSphereInfo*           LInfo = (E3dSphereInfo*)PInfo;

	switch(PAction)
	{
		case E_UPDATE:
			if(LDrawer->ResourcesGUI)
			{
				E3dResourcesGUI*  LGUI = (E3dResourcesGUI*)LDrawer->ResourcesGUI;

				XeToggleButton_SetState(LGUI->SmoothW, LInfo->Smooth, FALSE);
				XeToggleButton_SetState(LGUI->InsideOutW, LInfo->InsideOut, FALSE);
				XeToggleButton_SetState(LGUI->TrianglesW, LInfo->Triangles, FALSE);
				XeToggleButton_SetState(LGUI->OriginAtBottomW, LInfo->OriginAt == E3dBOTTOM, FALSE);
				XeToggleButton_SetState(LGUI->TangentsAlongMeridiansW, LInfo->TangentDir == E3dSphereSOUTH, FALSE);

				XeToggleButton_SetState(LGUI->TypeUVShperePolygonsW,          LInfo->Type == E3dSphereUVSPHERE_POLY,            FALSE);

#if !ERELEASE_BUILD
				XeToggleButton_SetState(LGUI->TypeUVEqualAreaSpherePolygonsBW, LInfo->Type == E3dSphereUVEQUAL_AREA_SPHERE_POLY, FALSE);
#endif

				XeToggleButton_SetState(LGUI->TypeGeoSpherePolygonsBW,         LInfo->Type == E3dSphereGEOSPHERE_POLY,           FALSE);

#if E3dFIBONACCI_ENABLE
				XeToggleButton_SetState(LGUI->TypeFibonacciPointsW,                 LInfo->Type == E3dSphereFIBONACCI,             FALSE);
#endif

				XeScale_SetValue_Coord(LGUI->RadiusW, LInfo->Radius, FALSE);
				XeScale_SetValue_Coord(LGUI->NorthMostLatitudeW, LInfo->NorthMostLatitude, FALSE);
				XeScale_SetValue_Coord(LGUI->SouthMostLatitudeW, LInfo->SouthMostLatitude, FALSE);

				_SetGUIToType(LInfo, LGUI);
			}
			else
			{
				XeWidget  LTopMatrixW = LDrawer->Drawer.DrawerW, LMatrixW, LW;
				XeWidget  LIndentWs[12];int  LIndentN;      // For indenting the ScaleWidgets
				XeWidget  LTraverseWs[12];int  LTraverseN;  // For TextField traversal
				EArg      Args[256];int  ArgCnt;

				E3dResourcesGUI*  LGUI;ENew(LGUI);LDrawer->ResourcesGUI = LGUI;

				EXeStart;
				EXeSetArg(XeNSpacingLeft, XeDrawer_LeftIndent);
				EXeSetArg(XeNSpacingRight, 4);
				EXeSetArg(XeNSpacingBottom, 4);
				Xe_SetValues(LTopMatrixW,  Args, ArgCnt);

				EXeStart;
				EXeSetArg(XeNBackgroundColorPtr, &Xe_BackgroundRGBAiColor);
				EXeSetArg(XeNOrientation, XeHORIZONTAL);
				EXeSetArg(XeNSpacingRight, 8);
				EXeSetArg(XeNColumns, 3);
				EXeSetArg(XeNMarginSizeX, 3);
				EXeSetArg(XeNSpacingX, 12);
				EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNAlignmentX, XeAlignBEGINNING);
				EXeSetArg(XeNResizableY, FALSE);
				LMatrixW = XeWidget_ManagedNew("LMatrixW", XeMatrixWidget_class, LTopMatrixW,  Args, ArgCnt);

				EXeStart;
				EXeSetArg(XeNBackgroundColorPtr, &Xe_BackgroundRGBAiColor);
				EXeSetArg(XeNForegroundColorPtr, &Xe_ForegroundRGBAiColor);
				EXeSetArg(XeNSelectColorPtr, &Xe_HighlightRGBAiColor);
				EXeSetArg(XeNShadowThickness, 0);
				EXeSetArg(XeNHighlightThickness, 0);
				EXeSetArg(XeNMarginSizeX, 0);
				EXeSetArg(XeNFontList, Xe_LabelFontList);
				EXeSetArg(XeNSet, LInfo->Smooth);
				LGUI->SmoothW = LW = XeWidget_ManagedNew("Smooth", XeToggleButton_class, LMatrixW,  Args, ArgCnt);
				XeAddCallback(LW, XeNValueChangedCallback, _DCB, (EPointer)LPanel);

				EXeStart;
				EXeSetArg(XeNBackgroundColorPtr, &Xe_BackgroundRGBAiColor);
				EXeSetArg(XeNForegroundColorPtr, &Xe_ForegroundRGBAiColor);
				EXeSetArg(XeNSelectColorPtr, &Xe_HighlightRGBAiColor);
				EXeSetArg(XeNShadowThickness, 0);
				EXeSetArg(XeNHighlightThickness, 0);
				EXeSetArg(XeNMarginSizeX, 0);
				EXeSetArg(XeNFontList, Xe_LabelFontList);
				EXeSetArg(XeNSet, LInfo->OriginAt == E3dBOTTOM);
				LGUI->OriginAtBottomW = LW = XeWidget_ManagedNew("Orig. at bottom", XeToggleButton_class, LMatrixW,  Args, ArgCnt);
				XeAddCallback(LW, XeNValueChangedCallback, _DCB, (EPointer)LPanel);

				EXeStart;
				EXeSetArg(XeNBackgroundColorPtr, &Xe_BackgroundRGBAiColor);
				EXeSetArg(XeNForegroundColorPtr, &Xe_ForegroundRGBAiColor);
				EXeSetArg(XeNSelectColorPtr, &Xe_HighlightRGBAiColor);
				EXeSetArg(XeNShadowThickness, 0);
				EXeSetArg(XeNHighlightThickness, 0);
				EXeSetArg(XeNMarginSizeX, 0);
				EXeSetArg(XeNFontList, Xe_LabelFontList);
				EXeSetArg(XeNSet, LInfo->InsideOut);
//printf("%s() %s:%d  %sLInfo->InsideOut%s %d\n", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, ECodeNormal, LInfo->InsideOut);fflush(stdout);
				LGUI->InsideOutW = LW = XeWidget_ManagedNew("Inside-out", XeToggleButton_class, LMatrixW,  Args, ArgCnt);
				XeAddCallback(LW, XeNValueChangedCallback, _DCB, (EPointer)LPanel);

				EXeStart;
				EXeSetArg(XeNBackgroundColorPtr, &Xe_BackgroundRGBAiColor);
				EXeSetArg(XeNForegroundColorPtr, &Xe_ForegroundRGBAiColor);
				EXeSetArg(XeNSelectColorPtr, &Xe_HighlightRGBAiColor);
				EXeSetArg(XeNShadowThickness, 0);
				EXeSetArg(XeNHighlightThickness, 0);
				EXeSetArg(XeNMarginSizeX, 0);
				EXeSetArg(XeNFontList, Xe_LabelFontList);
				EXeSetArg(XeNSet, LInfo->Triangles);
				LGUI->TrianglesW = LW = XeWidget_ManagedNew("Triangles", XeToggleButton_class, LMatrixW,  Args, ArgCnt);
				XeAddCallback(LW, XeNValueChangedCallback, _DCB, (EPointer)LPanel);

				EXeStart;
				EXeSetArg(XeNBackgroundColorPtr, &Xe_BackgroundRGBAiColor);
				EXeSetArg(XeNForegroundColorPtr, &Xe_ForegroundRGBAiColor);
				EXeSetArg(XeNSelectColorPtr, &Xe_HighlightRGBAiColor);
				EXeSetArg(XeNShadowThickness, 0);
				EXeSetArg(XeNHighlightThickness, 0);
				EXeSetArg(XeNMarginSizeX, 0);
				EXeSetArg(XeNFontList, Xe_LabelFontList);
				EXeSetArg(XeNSet, LInfo->TangentDir == E3dSphereSOUTH);
				XeToolTip8("Align tangents along meridians (vs. along parallels)");
				LGUI->TangentsAlongMeridiansW = LW = XeWidget_ManagedNew("Tg along meridians", XeToggleButton_class, LMatrixW,  Args, ArgCnt);
				XeAddCallback(LW, XeNValueChangedCallback, _DCB, (EPointer)LPanel);



// Type selector
//
				EXeStart;
				EXeSetArg(XeNRadioBehavior, TRUE);
				EXeSetArg(XeNOrientation, XeHORIZONTAL);
				EXeSetArg(XeNSpacingX, 7);
				EXeSetArg(XeNMarginSizeX, 3);EXeSetArg(XeNMarginSizeY, 5);
				EXeSetArg(XeNBackgroundColorPtr, &Xe_BackgroundRGBAiColor);
				EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNAlignmentX, XeAlignBEGINNING);EXeSetArg(XeNResizableY, FALSE);
				LMatrixW = XeWidget_ManagedNew("", XeMatrixWidget_class, LTopMatrixW,  Args, ArgCnt);

				EXeStart;
				EXeSetArg(XeNBackgroundColorPtr, &Xe_BackgroundRGBAiColor);
				EXeSetArg(XeNForegroundColorPtr, &Xe_ForegroundRGBAiColor);
				EXeSetArg(XeNFontList, Xe_LabelFontList);
				EXeSetArg(XeNResizableX, FALSE);
				EXeSetArg(XeNMarginSizeX, 0);EXeSetArg(XeNMarginSizeY, 0);
				XeWidget_ManagedNew("Type", XeLabelWidget_class, LMatrixW,  Args, ArgCnt);

				EXeStart;
				EXeSetArg(XeNShadowThickness, 0);
				EXeSetArg(XeNIndicatorOn, FALSE);
				EXeSetArg(XeNHighlightThickness, 0);
				EXeSetArg(XeNMarginSizeX, 0);EXeSetArg(XeNMarginSizeY, 0);
				EXeSetArg(XeNBackgroundColorPtr, &Xe_BackgroundRGBAiColor);
				EXeSetArg(XeNForegroundColorPtr, &Xe_ForegroundRGBAiColor);
				EXeSetArg(XeNFontList, Xe_LabelFontList);
				EXeSetArg(XeNSet, LInfo->Type == E3dSphereUVSPHERE_POLY);
				EXeSetArg(XeNResizableX, FALSE);
				EXeSetArg(XeNActivateOnArm, TRUE);
				EXeSetArg(XeNLabelType, XeIMAGE);EXeSetImages3(_Image_UVSphere);
				XeToolTip8("Latitude-longitude, Polygons");
				LGUI->TypeUVShperePolygonsW = LW = XeWidget_ManagedNew("", XeToggleButton_class, LMatrixW,  Args, ArgCnt);
				XeAddCallback(LW, XeNValueChangedCallback, _DCB, (EPointer)LPanel);


#if !ERELEASE_BUILD
				EXeStart;
				EXeSetArg(XeNShadowThickness, 0);
				EXeSetArg(XeNIndicatorOn, FALSE);
				EXeSetArg(XeNHighlightThickness, 0);
				EXeSetArg(XeNMarginSizeX, 0);EXeSetArg(XeNMarginSizeY, 0);
				EXeSetArg(XeNBackgroundColorPtr, &Xe_BackgroundRGBAiColor);
				EXeSetArg(XeNForegroundColorPtr, &Xe_ForegroundRGBAiColor);
				EXeSetArg(XeNFontList, Xe_LabelFontList);
				EXeSetArg(XeNSet, LInfo->Type == E3dSphereUVEQUAL_AREA_SPHERE_POLY);
				EXeSetArg(XeNResizableX, FALSE);
				EXeSetArg(XeNActivateOnArm, TRUE);
				EXeSetArg(XeNLabelType, XeIMAGE);EXeSetImages3(_Image_UVSphere);
				XeToolTip8("Equal-area Polygons");
				LGUI->TypeUVEqualAreaSpherePolygonsBW = LW = XeWidget_ManagedNew("UVE", XeToggleButton_class, LMatrixW,  Args, ArgCnt);
				XeAddCallback(LW, XeNValueChangedCallback, _DCB, (EPointer)LPanel);
#endif

				EXeStart;
				EXeSetArg(XeNShadowThickness, 0);
				EXeSetArg(XeNIndicatorOn, FALSE);
				EXeSetArg(XeNHighlightThickness, 0);
				EXeSetArg(XeNMarginSizeX, 0);EXeSetArg(XeNMarginSizeY, 0);
				EXeSetArg(XeNBackgroundColorPtr, &Xe_BackgroundRGBAiColor);
				EXeSetArg(XeNForegroundColorPtr, &Xe_ForegroundRGBAiColor);
				EXeSetArg(XeNFontList, Xe_LabelFontList);
				EXeSetArg(XeNSet, LInfo->Type == E3dSphereGEOSPHERE_POLY);
				EXeSetArg(XeNResizableX, FALSE);
				EXeSetArg(XeNActivateOnArm, TRUE);
				EXeSetArg(XeNLabelType, XeIMAGE);EXeSetImages3(_Image_GeoSphere);
				XeToolTip8("Geodesic sphere, Polygons");
				LGUI->TypeGeoSpherePolygonsBW = LW = XeWidget_ManagedNew("", XeToggleButton_class, LMatrixW,  Args, ArgCnt);
				XeAddCallback(LW, XeNValueChangedCallback, _DCB, (EPointer)LPanel);

#if E3dFIBONACCI_ENABLE
				EXeStart;
				EXeSetArg(XeNShadowThickness, 0);
				EXeSetArg(XeNIndicatorOn, FALSE);
				EXeSetArg(XeNHighlightThickness, 0);
				EXeSetArg(XeNMarginSizeX, 0);EXeSetArg(XeNMarginSizeY, 0);
				EXeSetArg(XeNBackgroundColorPtr, &Xe_BackgroundRGBAiColor);
				EXeSetArg(XeNForegroundColorPtr, &Xe_ForegroundRGBAiColor);
				EXeSetArg(XeNFontList, Xe_LabelFontList);
				EXeSetArg(XeNSet, LInfo->Type == E3dSphereFIBONACCI);
				EXeSetArg(XeNResizableX, FALSE);
				EXeSetArg(XeNActivateOnArm, TRUE);
				EXeSetArg(XeNLabelType, XeIMAGE);EXeSetImages3(_Image_UVSphere);
				XeToolTip8("Fibonacci points");
				LGUI->TypeFibonacciPointsW = LW = XeWidget_ManagedNew("Fibonacci", XeToggleButton_class, LMatrixW,  Args, ArgCnt);
				XeAddCallback(LW, XeNValueChangedCallback, _DCB, (EPointer)LPanel);
#endif


				LTraverseN = 0;
				LIndentN = 0;
				EXeStart;
				EXeSetArg(XeNTextFieldDigits, 8);EXeSetArg(XeNDecimalPoints, 4);
				EXeSetArg(XeNIncrement, 10);
				EXeSetArg(XeNResizableY, FALSE);
				LIndentWs[LIndentN++] = LTraverseWs[LTraverseN++] = LGUI->RadiusW = LW = XeScale_New_Coord(LTopMatrixW, "Radius",  Args, ArgCnt);
				XeScale_SetValueAndRanges_Coord(LW,  LInfo->Radius,  0.0, 10000000000.0,  0.0, 100.0,  FALSE);
				XeAddCallback(LW, XeNValueChangedCallback, _DCB, (EPointer)LPanel);


				EXeStart;
				EXeSetArg(XeNTextFieldDigits, 8);EXeSetArg(XeNDecimalPoints, 2);
				EXeSetArg(XeNIncrement, 10);
				EXeSetArg(XeNResizableY, FALSE);
				LIndentWs[LIndentN++] = LTraverseWs[LTraverseN++] = LGUI->NorthMostLatitudeW = LW = XeScale_New_Coord(LTopMatrixW, "Highest lat",  Args, ArgCnt);
				XeScale_SetValueAndRange_Coord(LW,  LInfo->NorthMostLatitude,  -90.0, 90.0,  FALSE);
				XeAddCallback(LW, XeNValueChangedCallback, _DCB, (EPointer)LPanel);

				EXeStart;
				EXeSetArg(XeNTextFieldDigits, 8);EXeSetArg(XeNDecimalPoints, 2);
				EXeSetArg(XeNIncrement, 10);
				EXeSetArg(XeNResizableY, FALSE);
				LIndentWs[LIndentN++] = LTraverseWs[LTraverseN++] = LGUI->SouthMostLatitudeW = LW = XeScale_New_Coord(LTopMatrixW, "Lowest lat",  Args, ArgCnt);
				XeScale_SetValueAndRange_Coord(LW,  LInfo->SouthMostLatitude,  -90.0, 90.0,  FALSE);
				XeAddCallback(LW, XeNValueChangedCallback, _DCB, (EPointer)LPanel);


				EXeStart;
				EXeSetArg(XeNTextFieldDigits, 8);
				EXeSetArg(XeNMinimum, 3);EXeSetArg(XeNMaximum, 100);EXeSetArg(XeNValue, 12);
				EXeSetArg(XeNIncrement, 1);
				EXeSetArg(XeNResizableY, FALSE);
				LIndentWs[LIndentN++] = LTraverseWs[LTraverseN++] = LGUI->USectionsW = LW = XeWidget_ManagedNew("U sections", XeScaleWidget_class, LTopMatrixW,  Args, ArgCnt);
				XeAddCallback(LW, XeNValueChangedCallback, _DCB, (EPointer)LPanel);

				EXeStart;
				EXeSetArg(XeNTextFieldDigits, 8);
				EXeSetArg(XeNMinimum, 2);EXeSetArg(XeNMaximum, 100);EXeSetArg(XeNValue, 6);
				EXeSetArg(XeNIncrement, 1);
				EXeSetArg(XeNResizableY, FALSE);
				LIndentWs[LIndentN++] = LTraverseWs[LTraverseN++] = LGUI->VSectionsW = LW = XeWidget_ManagedNew("V sections", XeScaleWidget_class, LTopMatrixW,  Args, ArgCnt);
				XeAddCallback(LW, XeNValueChangedCallback, _DCB, (EPointer)LPanel);

				XeWidgets_IndentFromLeft(LIndentWs, LIndentN, 0);

// Set up TextField traversal
//
				XeTraversal_SetRing(LTraverseWs, LTraverseN);

				_SetGUIToType(LInfo, LGUI);
			}
		break;
	}
}


//========================================
// Menu callback (create new sphere)
//========================================
static void _MCB(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dTpl_ObjectComponentActivateCB_Geometry(_ComponentClass);
}


//================================================
//================================================
static int _Init(E3dObject* PObject, E3dObjectComponent* PInfo)
{
	E3dSphereInfo*  LThis = (E3dSphereInfo*)PInfo;

	LThis->Type = E3dSphereUVSPHERE_POLY;
	LThis->USections = 16;
	LThis->VSections = 12;
	LThis->Iterations = 2;
	LThis->NVertices = 5;

	LThis->Smooth = TRUE;
	LThis->InsideOut = FALSE;
	LThis->Triangles = FALSE;

	LThis->Radius = 1.0;
	LThis->NorthMostLatitude = 90.0;
	LThis->SouthMostLatitude = -90.0;

	LThis->OriginAt = E3dCENTER;

	LThis->UnitCirclePoints = NULL;
	LThis->TangentDir = E3dSphereWEST;
//LThis->TangentDir = E3dSphereALONG_MERIDIANS;

	LThis->CollapseNorthPole = TRUE;
	LThis->CollapseSouthPole = TRUE;

//printf("%s() %s:%d  %sGREEN%s  %p\n", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, ECodeNormal, LThis);fflush(stdout);

	return(E_SUCCESS);
}


//================================================
//================================================
static int _Free(E3dObject* PObject, E3dObjectComponent* PInfo)
{
	E3dSphereInfo*  LThis = (E3dSphereInfo*)PInfo;

//printf("%s() %s:%d  %sGREEN%s  %p  UnitCirclePoints %p\n", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, ECodeNormal, LThis, LThis ? LThis->UnitCirclePoints : NULL);fflush(stdout);
	EFreeChkClr(LThis->UnitCirclePoints);

	return(E_SUCCESS);
}


//========================================
// Copy-constructor
//========================================
static void _Copy(const E3dObject* PSrcObject, const E3dObjectComponent* PSrcInfo,  E3dObject* PDstObject, E3dObjectComponent* PDstInfo)
{
	_Init(PDstObject, PDstInfo);

	const E3dSphereInfo*  LSrcInfo = (E3dSphereInfo*)PSrcInfo;
	E3dSphereInfo*        LDstInfo = (E3dSphereInfo*)PDstInfo;

	_CopyFields(LSrcInfo, LDstInfo);
}


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

	{
		E3dObjectComponentClass  LClass=
		{
			.Name=        "Sphere",
			.StructSize=  sizeof(E3dSphereInfo),
			.Resources=   EListS_s(_Resources),

			.Init=        _Init,
			.Free=        _Free,
			.Copy=        _Copy,
			.Update1=      _Update1,
			.GUI=         _GUI
		};

		if((_ComponentClass = E3dObjectComponent_ClassRegister(&LClass)) != NULL)
		{
			EpM_CreateIconStates3_sp(Sphere);E_Assign(_Image_Sphere, _ComponentClass->Icon);
			XePushButton_AddImg3(_ToolBarButton, Sphere, "Tool->Create", "Sphere", '\0', NULL, NULL, TRUE, "Create sphere\nRight button:  dialog box for this tool", _MCB, (EPointer)0);
			_MenuButton = XePushButton_Add(Xe_App, "Menu->Create", "Sphere", '\0', NULL, NULL, TRUE, NULL, _MCB, (EPointer)0);
		}
	}

	EpM_CreateIconStates3_sp(UVSphere);
	EpM_CreateIconStates3_sp(GeoSphere);


	return(E_SUCCESS);
}


//========================================
// Exit method of the plugin
//========================================
int E_PluginExit(const EBool PExiting)
{
	if(!PExiting || Xe_GUIFreeOnExit)
	{
		XeItemFree(_ToolBarButton);
		XeItemFree(_MenuButton);
	}

	if(_ComponentClass)
	{
//printf("%s() %s:%d  %s%s%s Instances %lld\n", __FUNCTION__, __FILE__, __LINE__,  ECodeGreen, _ComponentClass->Name, ECodeNormal,  (long long)_ComponentClass->Instances.Count);fflush(stdout);
		if(PExiting && _ComponentClass->Instances.Count > 0) return(E_BUSY);
		E3dObjectComponent_ClassDeactivate(_ComponentClass);_ComponentClass = NULL;
	}

	EpIconFree3(Sphere);
	EpIconFree3(UVSphere);
	EpIconFree3(GeoSphere);

	return(E_SUCCESS);
}
