//================================================================
// EQUINOX-3D(TM) plugin
//
// Boolean operations
// - Subtract, intersection and union
//
// AUTHOR:  Gabor Nagy
// DATE:    2026-Aug-12 19:53:30
//
// Copyright (C) 2014-2026 By Gabor Nagy. All rights reserved.
//================================================================
#include <math.h>
#include <stdio.h>
#include <stdlib.h>

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


#ifndef _E3D_Matrix_h
#include <E3D/Matrix.h>
#endif

#include <E3D/PolygonEditing.h>
#include <E3D/Raytracing.h>
#include <E3D/Select.h>

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


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

#include <Xe/Dialog.h>

#include <E3D/MergePolyGroups.h>

const float  _BooleanProgressbarSteps = 4.0;
const ETime  _PutUpProgressbarAfter_ms = 5000;

char*  E_PluginInfo = "Boolean operations\n - Subtract\n - Intersect\n - Union";

const EBool  _EdgePolygonIntersectMultiThreaded = TRUE;

#define PlTIMING  3  // Timing detail: 1: just total time per _Update() call  2: as 1 + accelerator build time etc.   3: timing stack

#if PlTIMING >= 3
#define ETimerENABLE  1
#include <ETiming.h>
extern ETmTimerStack  E3d_TimerStack;

#define tm_PUSH(p)  ETimerStack_Push(&E3d_TimerStack, p)
#define tm_POP()    ETimerStack_Pop(&E3d_TimerStack)

#else
#define tm_PUSH(p)
#define tm_POP()
#endif



extern XeWidget  Eqx_TopShellW;

static const EBool  _HighlightBadPolysInA = FALSE;
static const EBool  _HighlightBadPolysInB = FALSE;

static const char  _ToolName[] = "Boolean";


#define E3dDEBUG                      0
#define E3dDebugADDMESHBPOLYS         0
#define E3dDebugKEEP_VERTS_ON_MESHB   0
#define E3dDebugEDGES                 1

#define E3dPRESERVE_EDGES             1
#define E3dCHECKMESH0CLOSED           1  // Add UI for CheckIfMesh0IsClosed


#define E3dDOUBLE_CHECK_ADDING_MESHB_VERTEX  1  // If a Mesh1 Vertex needed by a Polygon in DstMesh is not added (e.g. a failed inside Mesh), add it in _PolygonRemapVertexIndices

#define EDGE_EDGE_INTERSECT  0


#if E3dDEBUG
static EBool  _Verbose = TRUE;
#endif



static EPlugin*  _Plugin = NULL;

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


static EBool  _SelectVertexMesh0Edge_Mesh1Surface = FALSE,
              _SelectVertexMesh1Edge_Mesh0Surface = FALSE;

static EBool  _AddResultToAllOriginalModels = TRUE;

static XeWidget  _HideOriginalsW;static EBool  _HideOriginals = TRUE;
static XeWidget  _SelectResultW; static EBool  _SelectResult  = TRUE;
static XeWidget  _HideResultW;   static EBool  _HideResult    = FALSE;


//#define E3dBOOLEAN_ACCELERATOR  E3dAccKDTREE
#define E3dBOOLEAN_ACCELERATOR  E3dAccBVH


#if E3dBOOLEAN_ACCELERATOR == E3dAccKDTREE
	#define _AcceleratorIsValid(p)   (p->KDTree != NULL)
	#define _RayIntersectScene       E3dRay_IntersectKDTree
#else
	#if E3dBOOLEAN_ACCELERATOR == E3dAccBVH
		#define _AcceleratorIsValid(p)   (p->BVH != NULL)
		#define _RayIntersectScene       E3dRay_IntersectBVH
	#else
		#define _AcceleratorIsValid(p)   (p->UniformGrid.Voxels != NULL)
		#define _RayIntersectScene       E3dRay_IntersectVoxels
	#endif
#endif



enum
{
	E3dUNION = 1,
	E3dSUBTRACTION,
	E3dINTERSECTION
};


typedef struct
{
	E3dVertexCore;

	uint8_t  State;
} E3dBVertex;



enum
{
	E3dORIGINAL_VERTEX = 1,
	E3dNEW_VERTEX_ON_ORIGINAL_EDGE,
	E3dNEW_VERTEX_ON_SURFACE
};


typedef struct E3dBVertexNode_S  E3dBVertexNode;

// VertexNode wrapper to contain both original and new VertexNodes on trimmed Polygons
//
struct E3dBVertexNode_S
{
	EIndex                NextOnOrigEdge;    // Next VertexNode that was on an original Edge of the Polygon being trimmed
	EIndex                NextNew;           // Next VertexNode that was created by an intersection 
	EIndex                VertexIndex;       // Vertex index in DstMesh
	EIndex                EdgeIndex;         // Edge index in current Mesh
	EIndex                BEdgeIndex;        // For edge-edge intersection, this is the Edge index in the other Mesh
	EBool                 Visited;

	uint8_t               State;
	uint8_t               Type;              // E3dORIGINAL_VERTEX, E3dNEW_VERTEX_ON_ORIGINAL_EDGE, E3dNEW_VERTEX_ON_SURFACE

	const E3dVertexNode*  VertexNode;        // If not NULL, this was an original VertexNode in the Polygon being trimmed, so we can just copy it

	E3dCoord3             EdgeDirection;
	E3dCoord3             Position;
	void*                 Face;
};


typedef struct
{
	E3dCoord3  BNormal;
	EBool      EdgeTrimmed;       // An Edge of this Polygon intersects a surface in the other Mesh
	EBool      SurfaceTrimmed;    // An Edge on the other Mesh intersects the surface of this Polygon

	EIndex*    EdgeIndices;       // Original Edges. Counts and contour structure match original Polygon's VertexNodes array.
} E3dBPolygon;



enum
{
	E3dON_MESHA_EDGE_MESHB_SURFACE = 1,    // Mesh0's Edge was intersected by a Polygon/RTriangle of Mesh1
	E3dON_MESHB_EDGE_MESHA_SURFACE,        // Mesh0's Polygon/RTriangle was intersected by an Edge of Mesh1
	E3dON_MESHA_EDGE_MESHB_EDGE            // Edge-Edge intersection point
};



typedef struct
{
	E3dCoord3            Position;
	EIndex               IndexInMesh;                       // Vertex index in DstMesh

	EIndex               EdgeIndex;E3dCoordinate  EdgeT;    // Edge intersection information
	const E3dRTriangle*  RTriangle;E3dCoord2  UV;           // Surface intersection information: RTriangle and barycentric coordinates on RTriangle, at the point of intersection
	const void*          Face;                              // RTriangle->Face to eliminate one level of dereferencing in high-frequency code

	uint8_t              Mode;                              // Tells how this BNewVertex was created : E3dON_MESHA_EDGE_MESHB_SURFACE, E3dON_MESHB_EDGE_MESHA_SURFACE
} E3dBNewVertex;




enum
{
	E3dON_VERTEX = 1,   // EdgeVertex is on an original Vertex of the other Mesh
	E3dON_EDGE,         // EdgeVertex is on an original Edge of the other Mesh
	E3dOUTSIDE,
	E3dINSIDE,
	E3dENTER,           // EdgeVertex marks entry point of Edge into other Mesh (or exit when Edge is read backward)
	E3dEXIT             // EdgeVertex marks exit point of Edge into other Mesh (or entry when Edge is read backward)
};


typedef struct
{
	EIndex         IndexInMesh;    // Vertex index in Mesh, LBVertices array etc.
	uint8_t        State;          // E3dON_VERTEX, E3dON_EDGE, E3dENTER, E3dEXIT with regard to the other Mesh

	E3dCoordinate  EdgeT;          // Distance from Edge starting point
	E3dCoord3      Position;       // Position at intersection
	const E3dRTriangle*  RTriangle;E3dCoord2  UV;           // Surface intersection information: RTriangle and barycentric coordinates on RTriangle, at the point of intersection
	void*          Face;           // Face (Polygon) hit on other Mesh
	EIndex         BEdgeIndex;     // Edge intersected on other Mesh

	uint8_t        Mode;                              // Tells how this BEdgeVertex was created : E3dON_MESHA_EDGE_MESHB_SURFACE, E3dON_MESHB_EDGE_MESHA_SURFACE
} E3dBEdgeVertex;
EListA_t(E3dBEdgeVertex);

typedef struct
{
#if E3dPRESERVE_EDGES
	E3dVertexIndex  Start, End;                   // Original start and end Vertex indices
#endif

	E3dCoord3      Origin, EndPoint;
	E3dCoord3      Direction;
	E3dCoordinate  Length;

	int            NumOfEdgeEdgeIntersections;    // Number of points on this Edge resulting from an intersection from Edges of the other Mesh. If > 0 NewVertices need to be sorted by T (Edge-Polygon intersections are ordered, if a K-D tree is used)

	EListA_s(, E3dBEdgeVertex, NewVertices);      // List of new indices. Count is 0, if Edge was not sliced up by intersections. The start and end vertex of the original Edge are not included.

	EListA(, E3dPolygon*, EdgeEdgePolygons);      // Filled out during Edge-Edge intersection. List of Polygons of the other Mesh that were on the intersected Edges of that Mesh.
} E3dBEdge;


typedef struct
{
	const E3dBVertex*     Mesh0BVertices;

	const E3dMesh*        SrcMesh;
	const E3dCoordinate*  SrcMesh_to_Mesh0Matrix;

	const E3dBVertex*     Mesh1BVertices;
	const E3dEdge*        Mesh1Edges;
	const E3dWingedEdge*  Mesh1WingedEdges;

	E3dMesh*              DstMesh;

	const E3dEdge*        Edges;
	const E3dBEdge*       BEdges;

	uint8_t               SurfacePointMode;
	EBool                 AddEdges;
	EBool                 InvertPolygon;


	E3dPolyGroup*         OldPolyGroup;
	unsigned int          VertexNodeSize;int  VertexNodeNormalOffset;

	uint8_t               NewPolyVertexMode;
	uint8_t               HoleVertexMode;
	EBool                 InvertInOut, KeepSurfaceTrimmedPoly;

	EListA_s(, E3dPolygon, NewPolygons);
	EListA(, E3dEdge,    NewEdges);

	EListA(, E3dBVertexNode, BVertexNodes);

	EList(, const E3dBNewVertex, BNewVertices);

	EListA(,E3dBVertexNode*, EntryPoints);    // Points where Edges enter the other Mesh
	EListA(,E3dBVertexNode*, ExitPoints);     // Points where Edges exit the other Mesh


	const E3dBPolygon*    BPolygon;
} E3dPolygonSlice;




static E3dObjectComponentClass*  _ObjectComponent_class = NULL;

typedef struct E3dInfo
{
	E3dObjectComponentCore;


// Parameters
//
	E3dModel*     Models[2];E3dGeometry*  Geometries[2];

	uint8_t       Operation;    // E3dUNION, E3dSUBTRACTION or E3dINTERSECTION

	EChangeFlags  ChangeRequestsCached;  // If ReactToShapeChange or ReactToTransformChange are FALSE, we cache requests coming in to _InputChanged here
	EBool         ReactToShapeChange, ReactToTransformChange;

	EBool         CheckIfMesh0IsClosed, RemoveUnusedVertices;

	EIndex        MeshAPolygonsFailed, MeshBPolygonsFailed;

// Internal data
//
	EInterface    InputInterface;  // This is added to SrcObjects and SrcModels as an output node

	EListA(, E3dBNewVertex, BNewVertices);

	E3dMergePolyGroups  MergePolyGroups;     // Merge PolyGroups with identical Materials
} E3dInfo;


#define ROffsetP(field)  (void*)(EPtrOffset(E3dInfo, field))
#define ROffsetI(field)  EPtrOffset(E3dInfo, field)
static EResource  _Resources[]=
{
	{ "Mesh0",          EResOBJECT,  ROffsetP(Geometries[0]) },
	{ "Mesh0Model",     EResOBJECT,  ROffsetP(Models[0]) },

	{ "Mesh1",          EResOBJECT,  ROffsetP(Geometries[1]) },
	{ "Mesh1Model",     EResOBJECT,  ROffsetP(Models[1]) },

	{ "Operation",      EResUINT8,   ROffsetP(Operation) },

	{ "Check if Mesh0 is closed",  EResBOOLEAN,  ROffsetP(CheckIfMesh0IsClosed) },
	{ "Remove unused Vertices",    EResBOOLEAN,  ROffsetP(RemoveUnusedVertices) },

	{ "ReactToShapeChange",        EResBOOLEAN,  ROffsetP(ReactToShapeChange) },
	{ "ReactToTransformChange",    EResBOOLEAN,  ROffsetP(ReactToTransformChange) },

	{ "Merge PolyGroups",                     EResBOOLEAN,         ROffsetP(MergePolyGroups.Active) },
	{ "Merge PolyGroups layout ST0",          EResUINT8,           ROffsetP(MergePolyGroups.LayoutST0.Mode) },
	{ "Merge PolyGroups layout ST0 spacing",  EResFLOAT32,         ROffsetP(MergePolyGroups.LayoutST0.Spacing) },
	{ "Merge PolyGroups layout ST1",          EResUINT8,           ROffsetP(MergePolyGroups.LayoutST1.Mode) },
	{ "Merge PolyGroups layout ST1 spacing",  EResFLOAT32,         ROffsetP(MergePolyGroups.LayoutST1.Spacing) },
	{ "Merge PolyGroups layout UV",           EResUINT8,           ROffsetP(MergePolyGroups.LayoutUV.Mode) },
	{ "Merge PolyGroups layout UV spacing",   EResFLOAT32,         ROffsetP(MergePolyGroups.LayoutUV.Spacing) },

	{ "MeshAPolygonsFailed",       EResINDEX,   ROffsetP(MeshAPolygonsFailed) },
	{ "MeshBPolygonsFailed",       EResINDEX,   ROffsetP(MeshBPolygonsFailed) },
};
#undef ROffsetP
#undef ROffsetI


typedef struct
{
	XeWidget  MeshNameLabelWs[2], MeshShowWs[2], MeshPickWs[2],
	          SubtractW, IntersectW, UnionW,
	          RemoveUnusedVerticesW, ReactToShapeChangeW, ReactToTransformChangeW,
	          StatusLabelW;

#if E3dCHECKMESH0CLOSED
	XeWidget  CheckIfMesh0IsClosedW;
#endif

	E3dMergePolyGroupsGUI  MergePolyGroups;
} E3dResourcesGUI;




//====================================================================================
// Find closest point to LPosition from the list PRelevantBNewVertices
// The point must
// - Lie on POtherFace
// - Be toward the inside of the current Polygon, as calculated by the formula (F)
//====================================================================================
static inline EIndex _FindClosestPointOnPolygon(const E3dCoord3 LPosition, const E3dCoord3 PPolygonNormal, const E3dCoord3 PEdgeDirection, const void* POtherFace, const E3dBNewVertex** PRelevantBNewVertices, const EIndex PRelevantBNewVertices_Count, const E3dWingedEdge* PWingedEdges, E3dCoordinate* PDistanceSq)
{
	EIndex  LIndexOfClosest = -1;

	E3dCoordinate  LMinDistanceSq = (E3dCoordinate)0.0;
	EIndex  LVI = 0;for(;LVI < PRelevantBNewVertices_Count;LVI++)
	{
		const E3dBNewVertex*  LBNewVertex = PRelevantBNewVertices[LVI];const E3dCoord3  LVtxPosition = LBNewVertex->Position;

//if(_Verbose) { printf("  %sVtx %lld (Edge %lld)%s", ECodeCyan, (long long)LBNewVertex->IndexInMesh, (long long)LBNewVertex->EdgeIndex, ECodeNormal); }

// Is this point on POtherFace?
//
		const E3dWingedEdge*  LWingedEdge = PWingedEdges + LBNewVertex->EdgeIndex;
		EIndex  LEC = 0;const int  LEN = LWingedEdge->Polygons_Count;const E3dEdgePolygonInfo*  LEdgePolygon = LWingedEdge->Polygons;for(;LEC < LEN;LEC++, LEdgePolygon++)
		{
//if(_Verbose) { printf(" [%p ~ %p]", LEdgePolygon->Polygon, POtherFace);fflush(stdout); }
			if(LEdgePolygon->Polygon == POtherFace)
			{
//if(_Verbose) { printf("  %s%lld%s", ECodeGreen, (long long)LBNewVertex->IndexInMesh, ECodeNormal); }

// Formula (F)
// Is this point toward the inside of the Polygon from the Edge?
// We check this by calculating the cross-product of the Edge's direction and the direction to the point from LBNewVertex, then doint a dot-product with that vector and the normal of LOldPolygon.
// If the dot product is positive, the point is toward the inside of the Polygon from the Edge.
//
				const E3dCoord3  E_Sub3i_vv(LEdgePointToNewPoint, LPosition, LVtxPosition);
				E3dCoord3        LCross;E_Cross3_vvv(LEdgePointToNewPoint, PEdgeDirection, LCross);

/*
				if(_Verbose)
				{
					printf("  %s%ld%s", ECodeGreen, (long long)LBNewVertex->IndexInMesh, ECodeNormal);

//printf("  % 5.2f % 5.2f % 5.2f",  XYZ_s(LEdgePointToNewPoint));fflush(stdout);
//printf("  % 5.2f % 5.2f % 5.2f  % 5.2f % 5.2f % 5.2f", XYZ_s(LPosition), XYZ_s(LVtxPosition));fflush(stdout);
//printf("  % 5.2f % 5.2f % 5.2f  % 5.2f % 5.2f % 5.2f", XYZ_s(LEdgePointToNewPoint), XYZ_s(PPolygonNormal));fflush(stdout);
//printf("  % 5.2f % 5.2f % 5.2f", XYZ_s(LCross));fflush(stdout);
				}
*/

				if(E_Dot3_vv(PPolygonNormal, LCross) >= 0.0)
				{
					const E3dCoordinate  LDistanceSq = E_Dot3_vv(LEdgePointToNewPoint, LEdgePointToNewPoint);
					if(LIndexOfClosest == -1 || LMinDistanceSq > LDistanceSq)
					{
						LMinDistanceSq = LDistanceSq;
						LIndexOfClosest = LVI;
					}
				}
				break;
			}
		}

	}
/*
 if(_Verbose) { printf("\n");fflush(stdout); }
*/

	*PDistanceSq = LMinDistanceSq;
	return(LIndexOfClosest);
}


static inline EIndex _FindClosestPointOnEdge(const E3dBVertexNode* PBVertexNode, const E3dCoord3 PPolygonNormal, const E3dCoord3 PEdgeDirection, const void* POtherFace, const E3dBVertexNode* PBVertexNodes, const EIndex PBVertexNodes_Count, const E3dWingedEdge* PWingedEdges, E3dCoordinate* PDistanceSq)
{
	EIndex                 LIndexOfClosest = -1;
	const E3dCoord3        LPosition = PBVertexNode->Position;
	const EIndex           LEdgeIndex = PBVertexNode->EdgeIndex;
	E3dCoordinate          LMinDistanceSq = (E3dCoordinate)0.0;
	const E3dBVertexNode*  LBVertexNode = PBVertexNodes;
//printf(" %s() %s:%d  EdgeDir % 5.2f % 5.2f % 5.2f", __FUNCTION__, __FILE__, __LINE__, XYZ_s(LEdgeDirection));fflush(stdout);
	EIndex  LVI = 0;for(;LVI < PBVertexNodes_Count;LVI++, LBVertexNode++)
	{
// Is this point on POtherFace?
//
//printf("  %s%d%s  (%lld %d %d)", ECodeGreen, (long long)LBVertexNode->VertexIndex, ECodeNormal, LBVertexNode->Face == POtherFace,  PBVertexNode != LBVertexNode, (LEdgeIndex == -1 || LEdgeIndex != LBVertexNode->EdgeIndex));fflush(stdout);
		if(LBVertexNode->Face == POtherFace)
		{
//printf("  %s%d%s  (%lld %d)", ECodeGreen, (long long)LBVertexNode->VertexIndex, ECodeNormal,  PBVertexNode != LBVertexNode, (LEdgeIndex == -1 || LEdgeIndex != LBVertexNode->EdgeIndex));fflush(stdout);
		 if(PBVertexNode != LBVertexNode && (LEdgeIndex == -1 || LEdgeIndex != LBVertexNode->EdgeIndex))
		 {
			 const E3dCoord3  LVtxPosition = LBVertexNode->Position;

// Formula (F)
// Is this point toward the inside of the Polygon from the Edge?
// We check this by calculating the cross-product of the Edge's direction and the direction to the point from LBVertexNode, then do a dot-product with that vector and the normal of LOldPolygon.
// If the dot product is positive, the point is toward the inside of the Polygon from the Edge.
//
			 const E3dCoord3  E_Sub3i_vv(LEdgePointToNewPoint, LPosition, LVtxPosition);
			 E3dCoord3        LCross;E_Cross3_vvv(LEdgePointToNewPoint, PEdgeDirection, LCross);

//printf("  %s%lld%s", ECodeGreen, (long long)LBVertexNode->VertexIndex, ECodeNormal);

//printf("  % 5.2f % 5.2f % 5.2f",  XYZ_s(LEdgePointToNewPoint));fflush(stdout);
//printf("  % 5.2f % 5.2f % 5.2f  % 5.2f % 5.2f % 5.2f", XYZ_s(LPosition), XYZ_s(LVtxPosition));fflush(stdout);
//printf("  % 5.2f % 5.2f % 5.2f  % 5.2f % 5.2f % 5.2f", XYZ_s(LEdgePointToNewPoint), XYZ_s(PPolygonNormal));fflush(stdout);
//printf("  % 5.2f % 5.2f % 5.2f", XYZ_s(LCross));fflush(stdout);

			 if(E_Dot3_vv(PPolygonNormal, LCross) >= 0.0)
			 {
				 const E3dCoordinate  LDistanceSq = E_Dot3_vv(LEdgePointToNewPoint, LEdgePointToNewPoint);
				 if(LIndexOfClosest == -1 || LMinDistanceSq > LDistanceSq)
				 {
					 LMinDistanceSq = LDistanceSq;
					 LIndexOfClosest = LVI;
				 }
			 }
		 }
	 }

	}
//printf("\n");fflush(stdout);

	*PDistanceSq = LMinDistanceSq;

	return(LIndexOfClosest);
}


static inline E3dCoord3 _BPolygonGetNormal(const E3dVertex* PDstMeshVertices, const E3dVertexSize PDstMeshVertexSize,  E3dBNewVertex** PContourBNewVertices, const EIndex PContourBNewVertices_Count)
{
	E3dCoord3  LNormal = E3dCoord3_s(0.0, 0.0, 0.0);
	{
		EIndex  LVI = 0;const EIndex  LVN = PContourBNewVertices_Count - 1;for(;LVI < LVN;LVI++)
		{
			const E3dBNewVertex*  LBNewVertex0 = PContourBNewVertices[LVI];
			const EIndex          LVertexIndex0 = LBNewVertex0->IndexInMesh;const E3dVertex*  LDstMeshVertex0 = EStruct_ByIndex(PDstMeshVertices, PDstMeshVertexSize, LVertexIndex0);
			const E3dCoord3       L0 = LDstMeshVertex0->Position;

			const E3dBNewVertex*  LBNewVertex1 = PContourBNewVertices[LVI + 1];
			const EIndex          LVertexIndex1 = LBNewVertex1->IndexInMesh;const E3dVertex*  LDstMeshVertex1 = EStruct_ByIndex(PDstMeshVertices, PDstMeshVertexSize, LVertexIndex1);
			const E3dCoord3       L1 = LDstMeshVertex1->Position;

//printf("%s() %s:%d   % 5.2f % 5.2f % 5.2f  ~  % 5.2f % 5.2f % 5.2f\n", __FUNCTION__, __FILE__, __LINE__, XYZ_s(L0), XYZ_s(L1));fflush(stdout);

			LNormal.X -= (L0.Z + L1.Z) * (L1.Y - L0.Y);
			LNormal.Y += (L0.Z + L1.Z) * (L1.X - L0.X);
			LNormal.Z -= (L0.Y + L1.Y) * (L1.X - L0.X);
		}

// The last Vertex pair is the last one and the 0th one
//
		{
			const E3dBNewVertex*  LBNewVertex0 = PContourBNewVertices[LVI];
			const EIndex          LVertexIndex0 = LBNewVertex0->IndexInMesh;const E3dVertex*  LDstMeshVertex0 = EStruct_ByIndex(PDstMeshVertices, PDstMeshVertexSize, LVertexIndex0);
			const E3dCoord3       L0 = LDstMeshVertex0->Position;

			const E3dBNewVertex*  LBNewVertex1 = PContourBNewVertices[0];
			const EIndex          LVertexIndex1 = LBNewVertex1->IndexInMesh;const E3dVertex*  LDstMeshVertex1 = EStruct_ByIndex(PDstMeshVertices, PDstMeshVertexSize, LVertexIndex1);
			const E3dCoord3       L1 = LDstMeshVertex1->Position;

//printf("%s() %s:%d   % 5.2f % 5.2f % 5.2f  ~  % 5.2f % 5.2f % 5.2f\n", __FUNCTION__, __FILE__, __LINE__, XYZ_s(L0), XYZ_s(L1));fflush(stdout);

			LNormal.X -= (L0.Z + L1.Z) * (L1.Y - L0.Y);
			LNormal.Y += (L0.Z + L1.Z) * (L1.X - L0.X);
			LNormal.Z -= (L0.Y + L1.Y) * (L1.X - L0.X);
		}
	}
	return(LNormal);
}


#define _DstMeshCheckVertexPoly(pSrcMesh, pVertexIndex,  pSrcMeshToDstmeshVertexIndexTable, pSrcMesh_to_DstMeshMatrix,  pDstMesh, pVertexNode) do\
{\
	const EIndex  lVertexIndex = pVertexIndex;\
printf("%s() %s:%d  %sPoly Vertex %lld has no equivalent in the destination Mesh%s", __FUNCTION__, __FILE__, __LINE__, ECodeYellow, (long long)lVertexIndex, ECodeNormal);fflush(stdout);\
\
	if(E3dDOUBLE_CHECK_ADDING_MESHB_VERTEX)\
	{\
printf(" %sADDING%s", ECodeGreen, ECodeNormal);fflush(stdout);\
		const E3dVertexSize  lSrcMeshVertexSize = pSrcMesh->VertexSize;\
		const E3dVertex*     lSrcVertex = EStruct_ByIndex(pSrcMesh->Vertices.Items, lSrcMeshVertexSize, lVertexIndex);\
\
		const EIndex         lNewIndexC = pDstMesh->Vertices.Count;\
		const E3dVertexSize  lDstMeshVertexSize = pDstMesh->VertexSize;\
		E3dVertex*           lDstVertex = EList_StructAddS_s(pDstMesh->Vertices, lDstMeshVertexSize);\
		E3dVertex_Copy(lSrcVertex, lSrcMeshVertexSize, &pSrcMesh->VertexAttributes,  lDstVertex, lDstMeshVertexSize, &pDstMesh->VertexAttributes);\
		lDstVertex->Flags |= E3dVtxSELECTED;\
\
		EMatrix_Transform3x4_vv(pSrcMesh_to_DstMeshMatrix, lSrcVertex->Position, lDstVertex->Position);\
\
		pSrcMeshToDstmeshVertexIndexTable[lVertexIndex]  = lNewIndexC;\
		pVertexNode->VertexID = lNewIndexC;\
printf("\n");fflush(stdout);\
	}\
	else\
	{\
printf("\n");fflush(stdout);\
		return(E_NOT_FOUND);\
	}\
\
} while(0)


#define _DstMeshCheckVertexEdge(pSrcMesh, pVertexIndex,  pSrcMeshToDstmeshVertexIndexTable, pSrcMesh_to_DstMeshMatrix,  pDstMesh, pVertexNode) do\
{\
	const EIndex  lVertexIndex = pVertexIndex;\
printf("%s() %s:%d  %sEdge Vertex %lld has no equivalent in the destination Mesh%s", __FUNCTION__, __FILE__, __LINE__, ECodeYellow, (long long)lVertexIndex, ECodeNormal);fflush(stdout);\
\
	if(E3dDOUBLE_CHECK_ADDING_MESHB_VERTEX)\
	{\
printf(" %sADDING%s", ECodeGreen, ECodeNormal);fflush(stdout);\
		const E3dVertexSize   lSrcMeshVertexSize = pSrcMesh->VertexSize;\
		const E3dVertex*      lSrcVertex = EStruct_ByIndex(pSrcMesh->Vertices.Items, lSrcMeshVertexSize, lVertexIndex);\
\
		const EIndex         lNewIndexC = pDstMesh->Vertices.Count;\
		const E3dVertexSize  lDstMeshVertexSize = pDstMesh->VertexSize;\
		const E3dVertexSize  lVertexCoreSize = E3dVertexCore_Size();\
		E3dVertex*           lDstVertex = EList_StructAddS_s(pDstMesh->Vertices, lDstMeshVertexSize);if(lDstMeshVertexSize > lVertexCoreSize) memset((uint8_t*)lDstVertex + LVertexCoreSize, 0, lDstMeshVertexSize - lVertexCoreSize);\
		E3dVertex_Copy(lSrcVertex, lSrcMeshVertexSize, &pSrcMesh->VertexAttributes,  lDstVertex, lDstMeshVertexSize, &pDstMesh->VertexAttributes);\
		lDstVertex->Flags |= E3dVtxSELECTED;\
\
		EMatrix_Transform3x4_vv(pSrcMesh_to_DstMeshMatrix, lSrcVertex->Position, lDstVertex->Position);\
\
		pSrcMeshToDstmeshVertexIndexTable[lVertexIndex]  = lNewIndexC;\
printf("\n");fflush(stdout);\
	}\
	else\
	{\
printf("\n");fflush(stdout);\
	}\
\
} while(0)


static inline int _PolygonRemapVertexIndices(E3dPolygon* PPolygon, const E3dVertexNodeSize PVertexNodeSize, EIndex* PSrcMeshToDstMeshVertexIndexTable,  const E3dMesh* PSrcMesh, const E3dCoordinate* PSrcMesh_to_DstMeshMatrix, E3dMesh* PDstMesh)
{
	EIndex  LVI = 0;const EIndex  LVN = PPolygon->NumOfVertexNodes;E3dVertexNode*  LVertexNode = PPolygon->VertexNodes;for(;LVI < LVN;LVI++, EStruct_Inc(LVertexNode, PVertexNodeSize))
	{
		const EIndex  LVertexIndex = LVertexNode->VertexID;if(LVertexIndex >= 0)
		{
			const EIndex  LNewIndex = PSrcMeshToDstMeshVertexIndexTable[LVertexIndex];if(LNewIndex >= 0) LVertexNode->VertexID = LNewIndex;
			else
			{
				_DstMeshCheckVertexPoly(PSrcMesh, LVertexIndex,  PSrcMeshToDstMeshVertexIndexTable, PSrcMesh_to_DstMeshMatrix,  PDstMesh, LVertexNode);
			}
		}
//printf(" %d -> %d", LVertexIndex, LMesh1ToDstMeshVertexIndexTable[LVertexIndex]);fflush(stdout);
	}

	return(E_SUCCESS);
}


//========================================================================================
// Add holes from the original Polygon, to the new Polygons, if they are applicable
//========================================================================================
static void _PolygonSliceAddOriginalHoles(E3dPolygon* POldPolygon, E3dPolygonSlice* PInfo, const EIndex PNewPolygons_StartIndex)
{
	const E3dVertexNodeSize  LVertexNodeSize = PInfo->VertexNodeSize;
	const E3dVertexNode*     LSrcVertexNode = EStruct_ByIndex(POldPolygon->VertexNodes, LVertexNodeSize, POldPolygon->NumOfExteriorVertices);
	EIndex                   LVI = POldPolygon->NumOfVertices;const EIndex  LVN = POldPolygon->NumOfVertexNodes;
	E3dPolygon*              LNewPolygonsInThisPass = PInfo->NewPolygons.Items + PNewPolygons_StartIndex;
	const EIndex             LNewPolygonsInThisPass_Count = PInfo->NewPolygons.Count - PNewPolygons_StartIndex;

	while(LVI < LVN)
	{
		E3dPolygon*   LHolePolygon = NULL;
		const EIndex  LVerticesInHole = -LSrcVertexNode->VertexID;

		if(LNewPolygonsInThisPass_Count > 1)
		{
// E3dFINISHME
// If we created more than one new Polygon out of OldPolygon (e.g. OldPolygon was split in half), we need to figure out which Polygon this hole belongs to
//
printf("%s() %s:%d %sIMPLEMENT ME%s find new Polygon to which this old hole belongs (%lld Polys)\n", __FUNCTION__, __FILE__, __LINE__, ECodeYellow, ECodeNormal, (long long)LNewPolygonsInThisPass_Count);fflush(stdout);
		}
		else
		{
// E3dFINISHME
// Check if the hole actually lies on the new Polygon (the part that was under the hole may have been cut away)
//
			LHolePolygon = LNewPolygonsInThisPass;
		}

		if(LHolePolygon)
		{
printf("%s() %s:%d\n", __FUNCTION__, __FILE__, __LINE__);fflush(stdout);

			const EIndex  LOldNumOfVertexNodes = LHolePolygon->NumOfVertexNodes;
			E3dVertexNode*  LVertexNodes = (E3dVertexNode*)ERealloc(LHolePolygon->VertexNodes, LVertexNodeSize * (LOldNumOfVertexNodes + LVerticesInHole + 1));if(LVertexNodes)
			{
				LHolePolygon->VertexNodes       = LVertexNodes;
				LHolePolygon->NumOfVertexNodes += LVerticesInHole + 1;
				LHolePolygon->NumOfVertices    += LVerticesInHole;

				E3dVertexNode*  LVertexNode = EStruct_ByIndex(LVertexNodes, LVertexNodeSize, LOldNumOfVertexNodes);
				memcpy(LVertexNode, LSrcVertexNode, LVertexNodeSize * (LVerticesInHole + 1));
			}
		}

		EStruct_Inc(LSrcVertexNode, LVertexNodeSize * (LVerticesInHole + 1));

		LVI += LVerticesInHole + 1;
	}
}


static int _PolygonCheck(const E3dPolygon* PPolygon, const E3dVertexNodeSize PVertexNodeSize, const EIndex PVertices_Count)
{
	EIndex  LVI = 0;const EIndex  LVN = PPolygon->NumOfVertexNodes;const E3dVertexNode*  LVertexNode = PPolygon->VertexNodes;for(;LVI < LVN;LVI++, EStruct_Inc(LVertexNode, PVertexNodeSize))
	{
		const E3dVertexIndex  LIndex = LVertexNode->VertexID;
		if(LIndex >= PVertices_Count) { printf("%s() %s:%d  %lld\n", __FUNCTION__, __FILE__, __LINE__, (long long)LIndex);fflush(stdout);return(E_TOO_LARGE); }
		else if(LIndex < 0)  // Is it a valid hole contour VertexCount?
		{
			const EIndex  LRemainingV = LVN - LVI - 1;
			if(-LIndex > LRemainingV) { printf("%s() %s:%d  %lld of %lld  VertexID = %lld\n", __FUNCTION__, __FILE__, __LINE__, (long long)LVI, (long long)LVN, (long long)LIndex);fflush(stdout);return(E_TOO_SMALL); }
		}
	}

	return(E_SUCCESS);
}


//========================================================================================================
// Add holes we may have cut, to the new Polygons, or new Polygons, depending on the boolean operation
//========================================================================================================
static void _PolygonSliceAddNewPolysOrHoles(const E3dPolygon* POldPolygon, E3dPolygonSlice* PInfo, const EIndex PNewPolygons_StartIndex,  const E3dBNewVertex** PRelevantBNewVertices, EIndex PRelevantBNewVertices_Count, const uint8_t PContourVertexMode, const EBool PAddHoles)
{
	const E3dVertexNodeSize  LVertexNodeSize = PInfo->VertexNodeSize;
	const int                LVertexNodeNormalOffset = PInfo->VertexNodeNormalOffset;
	E3dPolygon*              LNewPolygonsInThisPass = PInfo->NewPolygons.Items + PNewPolygons_StartIndex;
	const EIndex             LNewPolygonsInThisPass_Count = PInfo->NewPolygons.Count - PNewPolygons_StartIndex;
	const E3dWingedEdge*     LMesh1WingedEdges = PInfo->Mesh1WingedEdges;
	E3dMesh*                 LDstMesh = PInfo->DstMesh;
	const EIndex             LDstMeshVerticesN = LDstMesh->Vertices.Count;
	const E3dVertex*         LDstMeshVertices = LDstMesh->Vertices.Items;const EIndex  LDstMeshNVertices = LDstMesh->Vertices.Count;const E3dVertexSize  LDstMeshVertexSize = LDstMesh->VertexSize;
	const E3dBPolygon*       LBPolygon  = PInfo->BPolygon;

	EListAI(, E3dBNewVertex*, LContourBNewVertices);

	EIndex  LFirstBNewVertexIndex = -1;

	{
		EIndex  LI = 0;for(;LI < PRelevantBNewVertices_Count;LI++)
		{
			const E3dBNewVertex*  LBNewVertex = PRelevantBNewVertices[LI];
			if(LBNewVertex->Mode == PContourVertexMode) { LFirstBNewVertexIndex = LI;break; }
		}
	}

	if(LFirstBNewVertexIndex < 0) return;

	while(1)  // Contours
	{
// Find hole starting Vertex
//
		EIndex       					LBNewVertexIndex = LFirstBNewVertexIndex;
		const E3dBNewVertex*  LHoleStartBNewVertex = PRelevantBNewVertices[LBNewVertexIndex];
		E3dPolygon*           LPolygonToFollowOnMesh1;

		{
			const E3dBNewVertex*  LBNewVertex = PRelevantBNewVertices[LBNewVertexIndex];
			const EIndex          LEdgeIndex = LBNewVertex->EdgeIndex;
			const E3dWingedEdge*  LWingedEdge = LMesh1WingedEdges + LEdgeIndex;
			if(LWingedEdge->Polygons == NULL)
			{
#if E3dDEBUG
				if(_Verbose) { printf(" %sNo Polygons on WingedEdge %lld%s\n", ECodeRed, (long long)LEdgeIndex, ECodeNormal);fflush(stdout); }  // No enter/exit information on LBEdgeVertex
#endif
				goto DoneWithContours;
			}
			LPolygonToFollowOnMesh1 = LWingedEdge->Polygons[0].Polygon;
		}

		while(1)  // Vertices
		{
			E3dCoord3  LPosition;

			{
				const E3dBNewVertex*  LBNewVertex = PRelevantBNewVertices[LBNewVertexIndex];
				const EIndex          LVertexIndex = LBNewVertex->IndexInMesh;if(LVertexIndex >=0 && LVertexIndex < LDstMeshVerticesN)
				{
					const E3dVertex*  LDstMeshVertex = EStruct_ByIndex(LDstMeshVertices, LDstMeshVertexSize,  LVertexIndex);

					LPosition = LDstMeshVertex->Position;

// Remove starting Vertex from LRelevantBNewVertices (prune and search)
//
					EList_PointerRemoveByIndexNoRealloc(PRelevantBNewVertices, LBNewVertexIndex);

					EList_PointerAddA(LContourBNewVertices, 16, LBNewVertex);

					if(PRelevantBNewVertices_Count == 0)
					{
//printf("%s() %s:%d ALL DONE\n", __FUNCTION__, __FILE__, __LINE__);fflush(stdout);
						goto EndContour;
					}
				}
				else
				{
// LVertexIndex is out of bounds
//
printf("%s() %s:%d  <%s> Vertex index %s%lld is out of bounds%s %lld\n", __FUNCTION__, __FILE__, __LINE__, LDstMesh->Name, ECodeRed, (long long)LVertexIndex, ECodeNormal, (long long)LDstMeshVerticesN);fflush(stdout);
					return;
				}
			}


// Find next BNewVertex that defines the contour.
// It is the Vertex that:
// - also lies on LPolygonToFollowOnMesh1 and
// - is the closest to the current BNewVertex (LPolygonToFollowOnMesh1 may be concave and may intersect POldPolygon several times)
//
			EIndex         LNextBNewVertexIndex = -1;
			E3dCoordinate  LNextBVertexIndexDistanceSq = 0.0;

			EIndex  LI = LFirstBNewVertexIndex;for(;LI < PRelevantBNewVertices_Count;LI++)
			{
				const E3dBNewVertex*  LBNewVertex = PRelevantBNewVertices[LI];
				if(LBNewVertex->Mode == PContourVertexMode)
				{
					if((E3dPolygon*)LBNewVertex->Face == POldPolygon)
					{
						const EIndex          LEdgeIndex = LBNewVertex->EdgeIndex;
						const E3dWingedEdge*  LWingedEdge = LMesh1WingedEdges + LEdgeIndex;

						EIndex  LPI = 0;const EIndex  LPN = LWingedEdge->Polygons_Count;for(;LPI < LPN;LPI++)
						{
							if(LWingedEdge->Polygons[LPI].Polygon == LPolygonToFollowOnMesh1)
							{
//printf("%s() %s:%d  %lld\n", __FUNCTION__, __FILE__, __LINE__, (long long)LRelevantBNewVertices[LI]->IndexInMesh);fflush(stdout);

// Found potential next Mesh1 Edge that intersects POldPolygon
//
								if(LNextBNewVertexIndex < 0)
								{
// No candidate next BVertex yet, make this BVertex the candidate
//
									LNextBNewVertexIndex = LI;
									const EIndex     LVertexIndex = LBNewVertex->IndexInMesh;const E3dVertex*  LDstMeshVertex = EStruct_ByIndex(LDstMeshVertices, LDstMeshVertexSize,  LVertexIndex);
									E3dCoord3        LPos = LDstMeshVertex->Position;
									const E3dCoord3  E_Sub3i_vv(LDiff,  LPos, LPosition);const E3dCoordinate  LDistanceSq = E_Dot3_vv(LDiff, LDiff);
									LNextBVertexIndexDistanceSq = LDistanceSq;
								}
								else
								{
// There is already a candidate next BVertex, check if this BVertex is closer
//
									const EIndex     LVertexIndex = LBNewVertex->IndexInMesh;const E3dVertex*  LDstMeshVertex = EStruct_ByIndex(LDstMeshVertices, LDstMeshVertexSize,  LVertexIndex);
									E3dCoord3        LPos = LDstMeshVertex->Position;
									const E3dCoord3  E_Sub3i_vv(LDiff,  LPos, LPosition);const E3dCoordinate  LDistanceSq = E_Dot3_vv(LDiff, LDiff);
									if(LDistanceSq < LNextBVertexIndexDistanceSq)
									{
										LNextBNewVertexIndex = LI;
										LNextBVertexIndexDistanceSq = LDistanceSq;
									}
								}
							}
						}
					}
				}
			}

			if(LNextBNewVertexIndex >= 0)
			{
				const E3dBNewVertex*  LNextBNewVertex = PRelevantBNewVertices[LNextBNewVertexIndex];
				const EIndex          LEdgeIndex = LNextBNewVertex->EdgeIndex;
				const E3dWingedEdge*  LWingedEdge = LMesh1WingedEdges + LEdgeIndex;

				EIndex  LPI = 0;const EIndex  LPN = LWingedEdge->Polygons_Count;for(;LPI < LPN;LPI++)
				{
					if(LWingedEdge->Polygons[LPI].Polygon != LPolygonToFollowOnMesh1) { LPolygonToFollowOnMesh1 = LWingedEdge->Polygons[LPI].Polygon;break; }
				}

// Not found Polygon other than LPolygonToFollowOnMesh1 on LWingedEdge (it has only one Polygon, so the Mesh is not a manifold).
// Close hole contour.
//
				if(LPI == LPN) goto EndContour;

				LBNewVertexIndex = LNextBNewVertexIndex;

				if(LNextBNewVertex == LHoleStartBNewVertex)
				{
// Hole is complete
//
					break;
				}

				if(PRelevantBNewVertices_Count == 0)
				{
//printf("%s() %s:%d ALL DONE\n", __FUNCTION__, __FILE__, __LINE__);fflush(stdout);
					goto DoneWithContours;
				}
			}
			else
			{
printf("%s() %s:%d NO LNextBNewVertexIndex : END\n", __FUNCTION__, __FILE__, __LINE__);fflush(stdout);
				break;
			}
		}


// Check
//
EndContour:
		if(LContourBNewVertices_Count > 2)
		{
			if(PAddHoles)
			{
// Check winding order of hole and align it to the Polygon
//
				const E3dCoord3  LNormal = _BPolygonGetNormal(LDstMeshVertices, LDstMeshVertexSize,  LContourBNewVertices, LContourBNewVertices_Count);

//printf("%s() %s:%d   % 5.2f % 5.2f % 5.2f  ~  % 5.2f % 5.2f % 5.2f\n", __FUNCTION__, __FILE__, __LINE__, XYZ_s(LBPolygon->BNormal), XYZ_s(LNormal));fflush(stdout);

				const E3dCoordinate  LDot = E_Dot3_vv(LBPolygon->BNormal, LNormal);
//printf("%s() %s:%d  LDot %f\n", __FUNCTION__, __FILE__, __LINE__, LDot);fflush(stdout);
				if(LDot > 0.0)
				{
					EIndex  LVI = 0;const EIndex  LVN = LContourBNewVertices_Count / 2;for(;LVI < LVN;LVI++)
					{
						const EIndex    LOtherIndex = LContourBNewVertices_Count - LVI - 1;
						E3dBNewVertex*  LT = LContourBNewVertices[LVI];
						LContourBNewVertices[LVI] = LContourBNewVertices[LOtherIndex];
						LContourBNewVertices[LOtherIndex] = LT;
					}
				}


				E3dPolygon*  LHolePolygon = NULL;
				if(LNewPolygonsInThisPass_Count > 1)
				{
// If we created more than one new Polygon out of OldPolygon (e.g. OldPolygon was split in half), we need to figure out which Polygon this hole belongs to
//
// E3dFINISHME
printf("%s() %s:%d %sIMPLEMENT ME%s find new Polygon to which this hole belongs (%lld Polys)\n", __FUNCTION__, __FILE__, __LINE__, ECodeYellow, ECodeNormal, (long long)LNewPolygonsInThisPass_Count);fflush(stdout);
				}
				else
				{
// E3dFINISHME
// Check if the hole actually lies on the new Polygon (the part that was under the hole may have been cut away)
//
					LHolePolygon = LNewPolygonsInThisPass;
				}

// Add the hole for real
//
				if(LHolePolygon)
				{
					const EIndex  LOldNumOfVertexNodes = LHolePolygon->NumOfVertexNodes;
					E3dVertexNode*  LVertexNodes = (E3dVertexNode*)ERealloc(LHolePolygon->VertexNodes, LVertexNodeSize * (LOldNumOfVertexNodes + LContourBNewVertices_Count + 1));if(LVertexNodes)
					{
						LHolePolygon->VertexNodes = LVertexNodes;
						LHolePolygon->NumOfVertexNodes += LContourBNewVertices_Count + 1;
						LHolePolygon->NumOfVertices += LContourBNewVertices_Count;

						E3dVertexNode*  LVertexNode = EStruct_ByIndex(LVertexNodes, LVertexNodeSize, LOldNumOfVertexNodes);
						memset(LVertexNode, 0, LVertexNodeSize * (LContourBNewVertices_Count + 1));

// E3dFINISHME - interpolate VertexAttributes
//
						LVertexNode->VertexID = -LContourBNewVertices_Count;EStruct_Inc(LVertexNode, LVertexNodeSize);

						EIndex  LVI = 0;for(;LVI < LContourBNewVertices_Count;LVI++)
						{
							const EIndex  LVertexIndex = LContourBNewVertices[LVI]->IndexInMesh;
							LVertexNode->VertexID = LVertexIndex;
							EStruct_Inc(LVertexNode, LVertexNodeSize);

#if E3dPRESERVE_EDGES
							if(PInfo->AddEdges)
							{
								E3dEdge*  LNewEdge = EList_StructAddA(PInfo->NewEdges, 256);
								EIndex  LPrevVertexIndex = LVI > 0 ? LContourBNewVertices[LVI - 1]->IndexInMesh : LContourBNewVertices[LContourBNewVertices_Count - 1]->IndexInMesh;

								E3dEdge_Init(LNewEdge, LPrevVertexIndex, LVertexIndex);
								LNewEdge->Flags |= E3dEdgeSELECTED;  // Select new Edges
							}
#endif
						}
					}
				}
			}
			else
			{
// Add new Polygon
//
//printf("%s() %s:%d Add Poly %lld\n", __FUNCTION__, __FILE__, __LINE__, (long long)LContourBNewVertices_Count);fflush(stdout);
				E3dPolygon*  LNewPolygon = EList_StructAddAClr_s(PInfo->NewPolygons, 256);
				EMallocTClr(E3dVertexNode*, LVertexNodeSize * LContourBNewVertices_Count, LNewPolygon->VertexNodes);
				LNewPolygon->NumOfVertexNodes = LNewPolygon->NumOfVertices = LNewPolygon->NumOfExteriorVertices = LContourBNewVertices_Count;

				EIndex  LVI = 0;E3dVertexNode*  LVertexNode = LNewPolygon->VertexNodes;for(;LVI < LContourBNewVertices_Count;LVI++)
				{
					const E3dBNewVertex*  LBNewVertex = LContourBNewVertices[LVI];
					const EIndex  LVertexIndex = LBNewVertex->IndexInMesh;
					LVertexNode->VertexID = LVertexIndex;
//printf(" %lld", (long long)LVertexNode->VertexID);fflush(stdout);

#if E3dPRESERVE_EDGES
					if(PInfo->AddEdges)
					{
						E3dEdge*  LNewEdge = EList_StructAddA(PInfo->NewEdges, 256);
						EIndex    LPrevVertexIndex = LVI > 0 ? LContourBNewVertices[LVI - 1]->IndexInMesh : LContourBNewVertices[LContourBNewVertices_Count - 1]->IndexInMesh;

						E3dEdge_Init(LNewEdge, LPrevVertexIndex, LVertexIndex);
						LNewEdge->Flags |= E3dEdgeSELECTED;  // Select new Edges
					}
#endif


// E3dFINISHME - interpolate VertexAttributes

					const E3dRTriangle*   LRTriangle = LBNewVertex->RTriangle;const E3dCoord2  LUV = LBNewVertex->UV;
					const E3dRPolyGroup*  LRPolyGroup = LRTriangle->RPolyGroup;
					const E3dPolyGroup*   LPolyGroup = LRPolyGroup->PolyGroup;

					EStruct_Inc(LVertexNode, LVertexNodeSize);
				}
//printf("\n");fflush(stdout);

				E3dPolygon_UpdateNormal(LNewPolygon, LVertexNodeSize,  LDstMeshVertices, LDstMeshNVertices, LDstMeshVertexSize);

// Align LNewPolygon's winding order with that of POldPolygon
//
				const E3dCoordinate  LDot = E_Dot3_vv(LBPolygon->BNormal, LNewPolygon->Normal);

				if(LDot < 0.0) E3dPolygon_Invert(LNewPolygon, LVertexNodeSize, LVertexNodeNormalOffset);

				E3dPolygon_Triangulate(LNewPolygon, LVertexNodeSize,  LDstMeshVertices, LDstMeshNVertices, LDstMeshVertexSize,  TRUE);
			}


			if(PRelevantBNewVertices_Count == 0)
			{
//printf("%s() %s:%d ALL DONE\n", __FUNCTION__, __FILE__, __LINE__);fflush(stdout);
				goto DoneWithContours;
			}
		}
		else goto DoneWithContours;

		LContourBNewVertices_Count = 0;
	}


DoneWithContours:
	EList_FreeAChk(LContourBNewVertices);


// Re-triangulate Polygons we've added holes to
//
	if(PAddHoles)
	{
		EIndex  LPI = 0;E3dPolygon*  LPolygon = LNewPolygonsInThisPass;for(;LPI < LNewPolygonsInThisPass_Count;LPI++, LPolygon++)
		{
			if(LPolygon->NumOfVertices > LPolygon->NumOfExteriorVertices) E3dPolygon_Triangulate(LPolygon, LVertexNodeSize,  LDstMeshVertices, LDstMeshNVertices, LDstMeshVertexSize,  TRUE);
		}
	}
}


//========================================================================================================
// Chop up Polygon:
// Exterior contour
// - Start from the first VertexNode
// - Mark Edges already travelled. If we hit an already travelled Edge, terminate branch.
// (A)- If it's inside the other Mesh, walk Edges of contour until we find exit point (back-face ray hit), make this Vertex the first Vertex and go to (B)
// - If it's outside the other Mesh:
// (B) start building new Polygon
//   - If we get back to the first VertexNode, we're done with the contour
//   - If we hit an entry point to go inside the other Mesh, branch in two:
//     1) Branch one follows this path and go to (A)
//     2) Branch two follows path along new Edges, created by Mesh1 Polygon intersecting the current Polygon, always in the direction toward the surface of LPolygon
//========================================================================================================
static int _PolygonSlice(E3dPolygon* POldPolygon, E3dPolygonSlice* PInfo, EIndex* PMesh1ToDstMeshVertexIndexTable)
{
	const E3dPolyGroup*      LPolyGroup = PInfo->OldPolyGroup;
	const EIndex             LOPC = POldPolygon - LPolyGroup->Polygons.Items;
	const E3dMesh*           LSrcMesh = PInfo->SrcMesh;
	const E3dCoordinate*     LSrcMesh_to_Mesh0Matrix = PInfo->SrcMesh_to_Mesh0Matrix;
	E3dMesh*                 LDstMesh = PInfo->DstMesh;
	int                      LResult = E_SUCCESS;
	const E3dBVertex*        LBVertices = PInfo->Mesh0BVertices;

	const E3dEdge*           LMesh1Edges = PInfo->Mesh1Edges;
	const E3dWingedEdge*     LMesh1WingedEdges = PInfo->Mesh1WingedEdges;
	const E3dBVertex*        LMesh1BVertices = PInfo->Mesh1BVertices;

	const E3dEdge*           LEdges     = PInfo->Edges;
	const E3dBEdge*          LBEdges    = PInfo->BEdges;
	const E3dBPolygon*       LBPolygon  = PInfo->BPolygon;
	const EIndex*            LEdgeIndices = LBPolygon->EdgeIndices;
	const E3dVertexNodeSize  LVertexNodeSize = PInfo->VertexNodeSize;
	const int                LVertexNodeNormalOffset = PInfo->VertexNodeNormalOffset;

	const uint8_t            LSurfacePointMode = PInfo->SurfacePointMode;
	const EBool              LInvertInOut = PInfo->InvertInOut;

#if E3dDEBUG
	if(_Verbose)
	{
		printf("%s() %s:%d  <<%s%s%s>> Poly %lld", __FUNCTION__, __FILE__, __LINE__, ECodeCyan, LPolyGroup->Name, ECodeNormal, (long long)(POldPolygon - LPolyGroup->Polygons));fflush(stdout);
	}
#endif


	const EIndex  LNewPolygons_StartIndex = PInfo->NewPolygons.Count;

// Collect relevant BNewVertices:
// - It lies on POldPolygon
// - It's not on an Edge of POldPolygon
//
	EListANI(, const E3dBNewVertex*, LRelevantBNewVertices, PInfo->BNewVertices_Count);

	{
		E3dBNewVertex const**  LDst = LRelevantBNewVertices;
		EIndex  LI = 0;const EIndex  LN = PInfo->BNewVertices_Count;const E3dBNewVertex*  LBNewVertex = PInfo->BNewVertices;for(;LI < LN;LI++, LBNewVertex++)
		{
			if(LBNewVertex->Mode == LSurfacePointMode && LBNewVertex->Face == POldPolygon)
			{
				*LDst++ = LBNewVertex;
				LRelevantBNewVertices_Count++;
//printf(" %lld", (long long)(LI + PInfo->DstMeshVertices_Count - LN));fflush(stdout);
			}
		}
//printf("\n");fflush(stdout);
	}


//printf("%s() %s:%d  LRelevantBNewVertices_Count %lld\n", __FUNCTION__, __FILE__, __LINE__, (long long)LRelevantBNewVertices_Count);fflush(stdout);


// Build tree of BVertexNodes for traversal
// It's a binary tree with 1 or 2 branches at each VertexNode
//
	PInfo->BVertexNodes_Count = 0;
	EBool  LContourIntersected = FALSE;

	if(LBPolygon->EdgeTrimmed)
	{
		{
			EIndex                LNewVC = 0;
			EIndex                LVI = 0;const EIndex  LVN = POldPolygon->NumOfExteriorVertices;  // E3dFINISHME Only do the exterior for now
			const E3dVertexNode*  LVertexNode = POldPolygon->VertexNodes;

			PInfo->EntryPoints_Count = 0;
			PInfo->ExitPoints_Count  = 0;

			while(1)
			{
				const EIndex  LOldVertexIndex = LVertexNode->VertexID;if(LOldVertexIndex >= 0)
				{
					const E3dBVertex*  LBVertex = LBVertices + LOldVertexIndex;
					{
						const EIndex     LEdgeIndex = LEdgeIndices[LVI];
						const E3dEdge*   LEdge    = LEdges  + LEdgeIndex;
						const E3dBEdge*  LBEdge   = LBEdges + LEdgeIndex;
						EBool            LAddedOriginalVertexNode;

// Add original VertexNode of POldPolygon, if it's not inside Mesh1
//
						uint8_t  LInOutState = LBVertex->State;if(LInvertInOut) { if(LInOutState == E3dINSIDE) LInOutState = 0;else LInOutState = E3dINSIDE; }
						if(LInOutState != E3dINSIDE)
						{
#if E3dDEBUG
							if(_Verbose) { printf("\n  Vtx % 3lld  Edge %d (%lld)", (long long)LOldVertexIndex, (long long)LVI, (long long)LBEdge->NewVertices.Count);fflush(stdout); }
#endif
							E3dBVertexNode*  LBVertexNode = EList_StructAddA(PInfo->BVertexNodes, 64);
							LBVertexNode->NextOnOrigEdge  = LNewVC + 1;
							LBVertexNode->NextNew = -1;
							LBVertexNode->VertexIndex = LOldVertexIndex;
							LBVertexNode->EdgeIndex = LEdgeIndex;
							LBVertexNode->BEdgeIndex = -1;
							LBVertexNode->Visited = FALSE;
							LBVertexNode->State = LInOutState;
							LBVertexNode->VertexNode = LVertexNode;
							LBVertexNode->Position = LBVertex->Position;
							LBVertexNode->Face = NULL;
							LBVertexNode->Type = E3dORIGINAL_VERTEX;
							LNewVC++;
							LAddedOriginalVertexNode = TRUE;
						}
						else
						{
							LAddedOriginalVertexNode = FALSE;
#if E3dDEBUG
// Blue text means an original Vertex from POldPolygon is not used (cut off by the boolean operation)
//
							if(_Verbose) { printf("\n  Vtx %s% 3lld  Edge %d (%lld)%s",  ECodeBlue, (long long)LOldVertexIndex, (long long)LVI, (long long)LBEdge->NewVertices.Count, ECodeNormal);fflush(stdout); }
#endif
						}

						E3dCoord3  LEdgeDirection;

						if(LEdge->Start == LOldVertexIndex)
						{
// Edge goes forward on this Polygon
//
							E3dCoord3  LEdgeEnd = LBVertices[LEdge->End].Position;E_Sub3_vvv(LEdgeEnd, LBVertex->Position, LEdgeDirection);
							if(LAddedOriginalVertexNode) PInfo->BVertexNodes[LNewVC - 1].EdgeDirection = LEdgeDirection;

#if E3dDEBUG
							if(_Verbose) { printf(" >>");fflush(stdout); }
#endif
							EIndex  LI = 0;const EIndex  LN = LBEdge->NewVertices.Count;if(LN) LContourIntersected = TRUE;const E3dBEdgeVertex*  LBEdgeVertex = LBEdge->NewVertices.Items;for(;LI < LN;LI++, LBEdgeVertex++)
							{
								const uint8_t  LState = LBEdgeVertex->State;

								E3dBVertexNode*  LBVertexNode = EList_StructAddA(PInfo->BVertexNodes, 64);
								LBVertexNode->NextOnOrigEdge = LNewVC + 1;
								LBVertexNode->NextNew = -1;
								LBVertexNode->VertexIndex = LBEdgeVertex->IndexInMesh;
								LBVertexNode->EdgeIndex = LEdgeIndex;
								LBVertexNode->BEdgeIndex = LBEdgeVertex->BEdgeIndex;
								LBVertexNode->Visited = FALSE;
								LBVertexNode->VertexNode = NULL;    // E3dFIXME calculate this by interpolation along the Edge
								LBVertexNode->EdgeDirection = LEdgeDirection;
								LBVertexNode->Position = LBEdgeVertex->Position;
								LBVertexNode->Face = LBEdgeVertex->Face;
								LBVertexNode->Type = E3dNEW_VERTEX_ON_ORIGINAL_EDGE;

								switch(LState)
								{
									case E3dENTER:
										if(LInvertInOut) goto Exit0;
Enter0:
#if E3dDEBUG
										if(_Verbose) { printf(" %s>%lld%s", ECodeGreen, (long long)LBEdgeVertex->IndexInMesh, ECodeNormal);fflush(stdout); }
#endif
										EList_PointerAddA(PInfo->EntryPoints, 16, LBVertexNode);
									break;

									case E3dEXIT:
										if(LInvertInOut) goto Enter0;
Exit0:
#if E3dDEBUG
										if(_Verbose) { printf(" %s%lld>%s", ECodeGreen, (long long)LBEdgeVertex->IndexInMesh, ECodeNormal);fflush(stdout); }
#endif
										EList_PointerAddA(PInfo->ExitPoints, 16, LBVertexNode);
									break;

									default:
#if E3dDEBUG
										if(_Verbose) { printf(" %s%ld%s", ECodeRed, (long long)LBEdgeVertex->IndexInMesh, ECodeNormal);fflush(stdout); }  // No enter/exit information on LBEdgeVertex
#endif
									break;
								}

								LBVertexNode->State = LState;

								LNewVC++;
							}
						}
						else
						{
// Edge goes backward on this Polygon
//
							E3dCoord3  LEdgeEnd = LBVertices[LEdge->Start].Position;E_Sub3_vvv(LEdgeEnd, LBVertex->Position, LEdgeDirection);
							if(LAddedOriginalVertexNode) PInfo->BVertexNodes[LNewVC - 1].EdgeDirection = LEdgeDirection;

#if E3dDEBUG
							if(_Verbose) { printf(" <<");fflush(stdout);fflush(stdout); }
#endif
							EIndex  LI = 0;const EIndex  LN = LBEdge->NewVertices.Count;if(LN) LContourIntersected = TRUE;const E3dBEdgeVertex*  LBEdgeVertex = LBEdge->NewVertices.Items + LN - 1;for(;LI < LN;LI++, LBEdgeVertex--)
							{
								E3dBVertexNode*  LBVertexNode = EList_StructAddA(PInfo->BVertexNodes, 64);
								LBVertexNode->NextOnOrigEdge = LNewVC + 1;
								LBVertexNode->NextNew = -1;
								LBVertexNode->VertexIndex = LBEdgeVertex->IndexInMesh;
								LBVertexNode->EdgeIndex = LEdgeIndex;
								LBVertexNode->BEdgeIndex = LBEdgeVertex->BEdgeIndex;
								LBVertexNode->Visited = FALSE;
								LBVertexNode->VertexNode = NULL;    // E3dFIXME calculate this by interpolation along the Edge
								LBVertexNode->EdgeDirection = LEdgeDirection;
								LBVertexNode->Position = LBEdgeVertex->Position;
								LBVertexNode->Face = LBEdgeVertex->Face;
								LBVertexNode->Type = E3dNEW_VERTEX_ON_ORIGINAL_EDGE;

								uint8_t  LState;
								switch(LBEdgeVertex->State)
								{
									case E3dENTER:
										if(LInvertInOut) goto Exit1;
Enter1:
										LState = E3dEXIT;
#if E3dDEBUG
										if(_Verbose) { printf(" %s%lld>%s", ECodeGreen, (long long)LBEdgeVertex->IndexInMesh, ECodeNormal);fflush(stdout); }
#endif
										EList_PointerAddA(PInfo->ExitPoints, 16, LBVertexNode);
									break;

									case E3dEXIT:
										if(LInvertInOut) goto Enter1;
Exit1:
										LState = E3dENTER;
#if E3dDEBUG
										if(_Verbose) { printf(" %s>%lld%s", ECodeGreen, (long long)LBEdgeVertex->IndexInMesh, ECodeNormal);fflush(stdout); }
#endif
										EList_PointerAddA(PInfo->EntryPoints, 16, LBVertexNode);
									break;

									default:   LState = 0;
#if E3dDEBUG
										if(_Verbose) { printf(" %s%lld%s", ECodeRed, (long long)LBEdgeVertex->IndexInMesh, ECodeNormal);fflush(stdout); }  // No enter/exit information on LBEdgeVertex
#endif
									break;
								}

								LBVertexNode->State = LState;

//printf(" %s%lld%s", ECodeGreen, (long long)LBEdgeVertex->IndexInMesh, ECodeNormal);fflush(stdout);

								LNewVC++;
							}
						}
					}
				}

				EStruct_Inc(LVertexNode, LVertexNodeSize);LVI++;
				if(LVI >= LVN) break;
			}

// Terminate last BVertexNode
//
			if(LContourIntersected)
			{
				E3dBVertexNode*  LBVertexNode = PInfo->BVertexNodes + LNewVC - 1;
				LBVertexNode->NextOnOrigEdge = -1;
			}

#if E3dDEBUG
			if(_Verbose) { printf("\n");fflush(stdout); }
#endif


			const EIndex  LOriginalEdgeVertices_Count = LNewVC;

// Traverse secondary branches and finish constructing the tree
//
			if(LContourIntersected)
			{
				EIndex  LI = 0;const EIndex  LN = PInfo->EntryPoints_Count;for(;LI < LN;LI++)
				{
					E3dBVertexNode*  LBVertexNode   = PInfo->EntryPoints[LI];
					void*            LOtherFace     = LBVertexNode->Face;
					E3dCoord3        LPolygonNormal = LBPolygon->BNormal;

#if E3dDEBUG
					if(_Verbose)
					{
//						printf(" at Vertex %d (on-Poly Vtx %lld out of %lld)", (long long)LBVertexNode->VertexIndex, (long long)LFoundOnSurfaceIndex, (long long)LRelevantBNewVertices_Count);fflush(stdout);
//						printf("%s|%sat Vertex %2lld :", ECodeCyan, ECodeNormal, (long long)LBVertexNode->VertexIndex);fflush(stdout);
						printf("%s|%sat Vertex %2lld  LOtherFace %p  BEdgeIndex %ld :", ECodeCyan, ECodeNormal, (long long)LBVertexNode->VertexIndex, LOtherFace, (long long)LBVertexNode->BEdgeIndex);fflush(stdout);
					}
#endif

// Loop-A
//
					while(1)
					{
						const E3dCoord3  LPosition = LBVertexNode->Position;
						E3dBVertexNode*  LNewBVertexNode = NULL;

//printf("%s() %s:%d  LBVertexNode->VertexIndex %lld", __FUNCTION__, __FILE__, __LINE__, (long long)LBVertexNode->VertexIndex);fflush(stdout);

// Find internal new Vertex (on the surface of POldPolygon, at the closest point on LBVertexNode->Face, toward the inside of the Polygon from the Edge)
// In other words, we can not cross Edges of either POldPolygon or LBVertexNode->Face to reach this point.
//
						E3dCoord3  LEdgeDirection = LBVertexNode->EdgeDirection;

						{
							E3dCoordinate  LFoundOnSurfaceDistSq; const EIndex  LFoundOnSurfaceIndex  = _FindClosestPointOnPolygon(LPosition, LPolygonNormal, LEdgeDirection, LOtherFace,  LRelevantBNewVertices, LRelevantBNewVertices_Count,  LMesh1WingedEdges, &LFoundOnSurfaceDistSq);
							E3dCoordinate  LFoundOnOrigEdgeDistSq;const EIndex  LFoundOnOrigEdgeIndex = _FindClosestPointOnEdge(LBVertexNode, LPolygonNormal, LEdgeDirection, LOtherFace,  PInfo->BVertexNodes, LOriginalEdgeVertices_Count,    LMesh1WingedEdges, &LFoundOnOrigEdgeDistSq);

#if E3dDEBUG
//							EBool  LOldVerbose = _Verbose;_Verbose = FALSE;
#endif

							EBool  LUseOnSurfacePoint = FALSE;
							if(LFoundOnSurfaceIndex >= 0)
							{
								if(LFoundOnOrigEdgeIndex >= 0)
								{
									if(LFoundOnSurfaceDistSq < LFoundOnOrigEdgeDistSq) LUseOnSurfacePoint = TRUE;
								}
								else LUseOnSurfacePoint = TRUE;
							}

							if(LUseOnSurfacePoint)
							{
								const E3dBNewVertex*  LBNewVertex = LRelevantBNewVertices[LFoundOnSurfaceIndex];
#if E3dDEBUG
								if(_Verbose) { printf(" on-surface Vertex : %lld", (long long)LBNewVertex->IndexInMesh);fflush(stdout); }
#endif

								LBVertexNode->NextNew = PInfo->BVertexNodes_Count;

								LNewBVertexNode = EList_StructAddA(PInfo->BVertexNodes, 64);
								LNewBVertexNode->NextOnOrigEdge = -1;
								LNewBVertexNode->NextNew = -1;
								LNewBVertexNode->VertexIndex = LBNewVertex->IndexInMesh;
								LNewBVertexNode->EdgeIndex = -1;EClr_s(LNewBVertexNode->EdgeDirection);  // We switched from OldPolygon to Otherface, so we won't be on an original Edge of OldPolygon
								LNewBVertexNode->BEdgeIndex = -1;           // We're not on an Edge of the other Mesh either
								LNewBVertexNode->Visited = FALSE;
								LNewBVertexNode->State = 0;
								LNewBVertexNode->VertexNode = NULL;         // E3dFIXME calculate this by interpolation along the Edge
								LNewBVertexNode->Position = LBNewVertex->Position;
								LNewBVertexNode->Face = NULL;
								LNewBVertexNode->Type = E3dNEW_VERTEX_ON_SURFACE;

// Remove this point from LRelevantBNewVertices (prune and search)
//
								EList_PointerRemoveByIndexNoRealloc(LRelevantBNewVertices, LFoundOnSurfaceIndex);


// Make this point the next LBVertexNode
//
								LBVertexNode = LNewBVertexNode;

								{
									const EIndex  LEdgeIndex = LBNewVertex->EdgeIndex;const E3dWingedEdge*  LWingedEdge = LMesh1WingedEdges + LEdgeIndex;

									EIndex  LPI = 0;const EIndex  LPN = LWingedEdge->Polygons_Count;const E3dEdgePolygonInfo*  LEdgePolygonInfo = LWingedEdge->Polygons;for(;LPI < LPN;LPI++, LEdgePolygonInfo++)
									{
										E3dPolygon*  LPolygon = LEdgePolygonInfo->Polygon;if(LOtherFace != LPolygon)
										{
											LOtherFace = LPolygon;
											LPolygonNormal = LBPolygon->BNormal;
											const E3dEdge*  LEdge = LMesh1Edges + LEdgeIndex;

											EIndex  LSI, LEI;
											if(LEdgePolygonInfo->Flags & E3dPolyEDGE_BACKWARD) { LSI = LEdge->End;LEI = LEdge->Start; }
											else { LSI = LEdge->Start;LEI = LEdge->End; }

											const E3dBVertex*   LSBV = LMesh1BVertices + LSI;
											const E3dBVertex*   LEBV = LMesh1BVertices + LEI;

											E_Sub3_vvv(LEBV->Position, LSBV->Position, LEdgeDirection);
//printf("%s() %s:%d Edge dir :", __FUNCTION__, __FILE__, __LINE__);printf("  Vtx %lld -> %lld  (VtxNode %lld -> %lld)  Flags %08x  % 5.2f % 5.2f % 5.2f\n",  (long long)LEdge->Start, (long long)LEdge->End,  (long long)LEdgePolygonInfo->StartVertexNodeIndex, (long long)LEdgePolygonInfo->EndVertexNodeIndex,  LEdgePolygonInfo->Flags, XYZ_s(LEdgeDirection));fflush(stdout);
#if E3dDEBUG
//_Verbose = LOldVerbose;
#endif
											break;
										}
									}

									if(LPI == LPN)
									{
// LOtherFace has no neighbor on this Edge : Mesh1 is not closed
// Undo new BVertexNodes, except the first one after the OldPolygon Edge vertex where we started the loop and loop back to that OldPolygon Edge.
// We should "unweld" that Vertex...
//
#if E3dDEBUG
										printf(" %sIMPLEMENT ME%s No other Polygon on Edge...", ECodeYellow, ECodeNormal);fflush(stdout);
#endif
										break;
									}
								}
//break;
							}
							else
							{
// No candidate point found on the inside of POldPolygon, the next intersection must be between another Edge of POldPolygon and LOtherFace
//
								if(LFoundOnOrigEdgeIndex >= 0)
								{
#if E3dDEBUG
									const E3dBVertexNode*  LVtxNode = PInfo->BVertexNodes + LFoundOnOrigEdgeIndex;
									if(_Verbose) { printf(" %slooping back to Vertex %lld%s", ECodeGreen, (long long)LVtxNode->VertexIndex, ECodeNormal);fflush(stdout); }
#endif
									if(LNewBVertexNode)
									{
// If we created new BVertexNodes, connect the last one to this one to close this loop
//
										LNewBVertexNode->NextNew = LFoundOnOrigEdgeIndex;
									}
									else LBVertexNode->NextNew = LFoundOnOrigEdgeIndex;  // No new BVertexNodes on this branch, LOtherFace cut all the way through LOldPolygon
								}
								else
								{
#if E3dDEBUG
									if(_Verbose) { printf(" %sERROR finding closest relevant vertex on Edge out of %lld%s\n", ECodeRed, (long long)LOriginalEdgeVertices_Count, ECodeNormal);fflush(stdout); }
#endif
									LResult = E_UNKNOWN_ERROR;
									goto PolyExteriorsDone;
								}

// We found our way back to an original Edge of OldPolygon, or failed. Either way, we're done with Loop-A
//
								break;
							}
						}
					}
#if E3dDEBUG
					if(_Verbose) { printf("\n");fflush(stdout); }
#endif
				}
			}
		}


// Add new Polygons to NewPolyGroup
//
#if E3dDEBUG
		if(_Verbose) { printf("%s() %s:%d Building Polygons\n", __FUNCTION__, __FILE__, __LINE__);fflush(stdout); }
#endif

		if(PInfo->BVertexNodes_Count)
		{
			E3dBVertexNode*  LBVertexNodes = PInfo->BVertexNodes;const EIndex  LBVertexNodes_Count = PInfo->BVertexNodes_Count;
			E3dBVertexNode*  LLastBVertexNode = LBVertexNodes + LBVertexNodes_Count - 1;
			E3dBVertexNode*  LNextContourBVertexNode = LBVertexNodes;

			while(1)
			{
				E3dPolygon*      LNewPolygon = EList_StructAddAClr_s(PInfo->NewPolygons, 256);
				E3dBVertexNode*  LBVertexNode = LNextContourBVertexNode;

#if E3dPRESERVE_EDGES
				E3dBVertexNode*  LPrevBVertexNode = NULL;
				E3dBVertexNode*  LFirstBVertexNode = LBVertexNode;

//				if(!PInfo->AddEdges) LNewPolygon->Flags |= E3dPolygonSELECTED;
#endif
				for(;!LBVertexNode->Visited;)
				{
#if E3dDEBUG
					if(_Verbose)
					{
						switch(LBVertexNode->Type)
						{
							case E3dORIGINAL_VERTEX:              printf(" %s%lld%s", ECodeNormal, (long long)LBVertexNode->VertexIndex, ECodeNormal);fflush(stdout);break;
							case E3dNEW_VERTEX_ON_ORIGINAL_EDGE:  printf(" %s%lld%s", ECodeGreen,  (long long)LBVertexNode->VertexIndex, ECodeNormal);fflush(stdout); break;
							case E3dNEW_VERTEX_ON_SURFACE:        printf(" %s%lld%s", ECodeYellow, (long long)LBVertexNode->VertexIndex, ECodeNormal);fflush(stdout);break;
							default:  /* Uninitialized Type */    printf(" %s%lld%s", ECodeRed,    (long long)LBVertexNode->VertexIndex, ECodeNormal);fflush(stdout);break;
						}
					}
#endif

#if E3dPRESERVE_EDGES





#if E3dDebugEDGES
if(0)
{
/*
     432 :  0.369116,  0.251822, -1.540000
     431 :  0.307000,  0.260000, -1.540000
     841 :  0.307000,  0.260000, -1.488811
     842 :  0.369116,  0.251822, -1.486561

     432 :  0.369116,  0.251822, -1.540000
     431 :  0.307000,  0.260000, -1.540000
     841 :  0.307000,  0.260000, -1.488811
     842 :  0.369116,  0.251822, -1.486561


     841 :  0.307000,  0.260000, -1.488811
     431 :  0.307000,  0.260000, -1.540000
     432 :  0.369116,  0.251822, -1.540000
     842 :  0.369116,  0.251822, -1.486561
*/


	if(LPrevBVertexNode && strcmp(LPolyGroup->Name, "pg1") == 0 && LOPC == 30)
	{
LNewPolygon->Flags |= E3dPolygonSELECTED;
		const EIndex  LVertexIndex = PMesh1ToDstMeshVertexIndexTable[LBVertexNode->VertexIndex];
		E3dVertex*  LVertex = EStruct_ByIndex(LDstMesh->Vertices.Items, LDstMesh->VertexSize, LVertexIndex);
LVertex->Flags |= E3dVtxSELECTED;
//		printf("%s() %s:%d  <<%s/%lld>> %lld-%lld\n", __FUNCTION__, __FILE__, __LINE__, LPolyGroup->Name, (long long)LOPC, (long long)LPrevBVertexNode->VertexIndex, (long long)LVertexIndex);fflush(stdout);
		printf("    %s% 3lld%s : % f, % f, % f\n", ECodeCyan, (long long)LVertexIndex, ECodeNormal, XYZ_s(LVertex->Position));

	}
}
#endif





					if(PInfo->AddEdges && LBVertexNode->Type != E3dORIGINAL_VERTEX && LPrevBVertexNode && LPrevBVertexNode->Type != E3dORIGINAL_VERTEX)
//					if(LBVertexNode->Type != E3dORIGINAL_VERTEX && LPrevBVertexNode && LPrevBVertexNode->Type != E3dORIGINAL_VERTEX)
					{

						E3dEdge*  LNewEdge = EList_StructAddA(PInfo->NewEdges, 256);
						E3dEdge_Init(LNewEdge, LPrevBVertexNode->VertexIndex, LBVertexNode->VertexIndex);
						LNewEdge->Flags |= E3dEdgeSELECTED;  // Select new Edges
//						if(!PInfo->AddEdges) LNewEdge->Flags |= E3dEdgeSELECTED;
					}
					LPrevBVertexNode = LBVertexNode;
#endif

					char*  LVtxNodes = ERealloc(LNewPolygon->VertexNodes, (LNewPolygon->NumOfVertexNodes + 1) * LVertexNodeSize);if(LVtxNodes)
					{
						LNewPolygon->VertexNodes = (E3dVertexNode*)LVtxNodes;
						E3dVertexNode*  LVertexNode = (E3dVertexNode*)(LVtxNodes + LNewPolygon->NumOfVertexNodes * LVertexNodeSize);
						LNewPolygon->NumOfVertexNodes      += 1;
						LNewPolygon->NumOfVertices         += 1;
						LNewPolygon->NumOfExteriorVertices += 1;
						if(LBVertexNode->VertexNode)
						{
							memcpy(LVertexNode, LBVertexNode->VertexNode, LVertexNodeSize);
						}
						else LVertexNode->VertexID = LBVertexNode->VertexIndex;
					}

					LBVertexNode->Visited = TRUE;

//printf(" [%lld %lld]", (long long)LBVertexNode->NextNew, (long long)LBVertexNode->NextOnOrigEdge);fflush(stdout);
					if(LBVertexNode->NextNew >= 0)
					{
						LBVertexNode = LBVertexNodes + LBVertexNode->NextNew;
					}
					else if(LBVertexNode->NextOnOrigEdge >= 0)
					{
						LBVertexNode = LBVertexNodes + LBVertexNode->NextOnOrigEdge;
					}
					else break;
				}

#if E3dPRESERVE_EDGES
				LBVertexNode = LFirstBVertexNode;


#if E3dDebugEDGES
if(0)
{
	if(LPrevBVertexNode && strcmp(LPolyGroup->Name, "pg1") == 0 && LOPC == 30)
	{
		const EIndex  LVertexIndex = PMesh1ToDstMeshVertexIndexTable[LBVertexNode->VertexIndex];
		E3dVertex*  LVertex = EStruct_ByIndex(LDstMesh->Vertices.Items, LDstMesh->VertexSize, LVertexIndex);

LVertex->Flags |= E3dVtxSELECTED;
//		printf("%s() %s:%d  <<%s/%lld>> %lld-%lld\n", __FUNCTION__, __FILE__, __LINE__, LPolyGroup->Name, (long long)LOPC, (long long)LPrevBVertexNode->VertexIndex, (long long)LVertexIndex);fflush(stdout);
		printf("    %s% 3lld%s : % f, % f, % f\n", ECodeCyan, (long long)LVertexIndex, ECodeNormal, XYZ_s(LVertex->Position));
	}
}
#endif


					if(PInfo->AddEdges && LBVertexNode->Type != E3dORIGINAL_VERTEX && LPrevBVertexNode && LPrevBVertexNode->Type != E3dORIGINAL_VERTEX)
//					if(LBVertexNode->Type != E3dORIGINAL_VERTEX && LPrevBVertexNode && LPrevBVertexNode->Type != E3dORIGINAL_VERTEX)
					{
						E3dEdge*  LNewEdge = EList_StructAddA(PInfo->NewEdges, 256);
						E3dEdge_Init(LNewEdge, LPrevBVertexNode->VertexIndex, LBVertexNode->VertexIndex);
						LNewEdge->Flags |= E3dEdgeSELECTED;  // Select new Edges
//						if(!PInfo->AddEdges) LNewEdge->Flags |= E3dEdgeSELECTED;
					}
#endif



#if E3dDEBUG
				if(_Verbose) { printf("\n");fflush(stdout); }
#endif

				if(LNewPolygon->NumOfVertexNodes == 0)
				{
					PInfo->NewPolygons.Count -= 1;
					assert(LNewPolygon->NumOfVertexNodes > 0);
				}


// Find next un-visited BVertexNode
//
//printf("%s() %s:%d IN  %d of %lld\n", __FUNCTION__, __FILE__, __LINE__, (long long)(LNextContourBVertexNode - LBVertexNodes), (long long)LBVertexNodes_Count);fflush(stdout);
				while(1)
				{
					LNextContourBVertexNode++;
					if(LNextContourBVertexNode > LLastBVertexNode) goto PolyExteriorsDone;
					if(!LNextContourBVertexNode->Visited) break;
				}
//printf("%s() %s:%d OUT\n", __FUNCTION__, __FILE__, __LINE__);fflush(stdout);
			}
		}
	}
	else if(LBPolygon->SurfaceTrimmed)
	{
		if(PInfo->KeepSurfaceTrimmedPoly)
		{
			E3dPolygon*  LNewPolygon = EList_StructAddA_s(PInfo->NewPolygons, 256);
			E3d_PolygonCopy(POldPolygon, LVertexNodeSize,  LNewPolygon);
		}
	}


PolyExteriorsDone:
// Add holes from POldPolygon to resulting Polygons, if any
//
	{
		const EIndex  LNewPolygonsInThisPass_Count = PInfo->NewPolygons.Count - LNewPolygons_StartIndex;if(LNewPolygonsInThisPass_Count)
		{
			if(POldPolygon->NumOfVertices > POldPolygon->NumOfExteriorVertices) _PolygonSliceAddOriginalHoles(POldPolygon, PInfo, LNewPolygons_StartIndex);


// Add holes created by cuts to resulting Polygons, if any
//
// The remaining LRelevantBNewVertices are the hole Vertex candidates, as some of them lie on the surface of DstMesh Polygons
//
			if(LRelevantBNewVertices_Count)
			{
/*
				LRelevantBNewVertices_Count = 0;

				{
					EIndex  LI = 0;const EIndex  LN = PInfo->BNewVertices_Count;const E3dBNewVertex*  LBNewVertex = PInfo->BNewVertices;E3dBNewVertex const**  LDst = LRelevantBNewVertices;for(;LI < LN;LI++, LBNewVertex++)
					{
						if(LBNewVertex->Mode == PInfo->HoleVertexMode && LBNewVertex->Face == POldPolygon)
						{
							*LDst++ = LBNewVertex;
							LRelevantBNewVertices_Count++;
						}
					}
				}
*/

				_PolygonSliceAddNewPolysOrHoles(POldPolygon, PInfo, LNewPolygons_StartIndex,  LRelevantBNewVertices, LRelevantBNewVertices_Count, PInfo->HoleVertexMode, TRUE);
			}
		}



//		if(PInfo->NewPolyVertexMode == E3dON_MESHA_EDGE_MESHB_SURFACE)
		if(PInfo->NewPolyVertexMode)
		{
/*
			LRelevantBNewVertices_Count = 0;

			{
				const E3dBNewVertex*   LBNewVertex = PInfo->BNewVertices;
				E3dBNewVertex const**  LDst = LRelevantBNewVertices;
				EIndex  LI = 0;const EIndex  LN = PInfo->BNewVertices_Count;for(;LI < LN;LI++, LBNewVertex++)
				{
					if(LBNewVertex->Mode == PInfo->NewPolyVertexMode && LBNewVertex->Face == POldPolygon)
					{
						*LDst++ = LBNewVertex;
						LRelevantBNewVertices_Count++;
					}
				}
			}
*/

			if(LRelevantBNewVertices_Count)
			{
				_PolygonSliceAddNewPolysOrHoles(POldPolygon, PInfo, LNewPolygons_StartIndex,  LRelevantBNewVertices, LRelevantBNewVertices_Count, PInfo->NewPolyVertexMode, FALSE);
			}
		}


// Finish setting up the Polygons created in this pass
// - Remap Vertex indices, if necessary
// - Invert Polygons, if necessary
// - Compute Polygon Normals
// - Triangulate Polygons
//
		const EBool  LInvertPolygon = PInfo->InvertPolygon;

		EIndex  LPI = 0;E3dPolygon*  LNewPolygon = PInfo->NewPolygons.Items + LNewPolygons_StartIndex;for(;LPI < LNewPolygonsInThisPass_Count;LPI++, LNewPolygon++)
		{
			if(LNewPolygon->NumOfVertices > 2)
			{
				if(PMesh1ToDstMeshVertexIndexTable)
				{
					if(_PolygonRemapVertexIndices(LNewPolygon, LVertexNodeSize, PMesh1ToDstMeshVertexIndexTable,  LSrcMesh, LSrcMesh_to_Mesh0Matrix, LDstMesh) == E_SUCCESS)
					{
					}
				}

				if(LInvertPolygon) E3dPolygon_Invert(LNewPolygon, LVertexNodeSize, LVertexNodeNormalOffset);

				const E3dVertex*  LDstMeshVertices = LDstMesh->Vertices.Items;const EIndex  LDstMeshNVertices = LDstMesh->Vertices.Count;const E3dVertexSize  LDstMeshVertexSize = LDstMesh->VertexSize;

assert(LNewPolygon != NULL);
				const int  LPolygonCheckResult = _PolygonCheck(LNewPolygon, LVertexNodeSize, LDstMesh->Vertices.Count);
				if(LPolygonCheckResult != E_SUCCESS)
				{
printf("%s() %s:%d  %s<<%s>> LOPC %lld  New Polygon index %lld  _PolygonCheck() failed. LNewPolygon->NumOfVertices %lld  POldPolygon->ITriangles_Count %lld%s %s\n", __FUNCTION__, __FILE__, __LINE__, ECodeRed, LPolyGroup->Name, (long long)LOPC, (long long)(LNewPolygon - PInfo->NewPolygons.Items), (long long)LNewPolygon->NumOfVertices, (long long)POldPolygon->ITriangles_Count, ECodeNormal,
LPolygonCheckResult == E_TOO_SMALL ? " VertexID too small" : LPolygonCheckResult == E_TOO_LARGE ? " VertexID too big" : "-"
);fflush(stdout);
					goto Error;
				}

				E3dPolygon_UpdateNormal(LNewPolygon, LVertexNodeSize,  LDstMeshVertices, LDstMeshNVertices, LDstMeshVertexSize);
				if(E3dPolygon_Triangulate(LNewPolygon, LVertexNodeSize,  LDstMeshVertices, LDstMeshNVertices, LDstMeshVertexSize,  TRUE) < 1)
				{
					if(_HighlightBadPolysInA) POldPolygon->Flags |= E3dPolygonSELECTED;

					LResult = E_CORRUPT_DATA;

{
printf("%s() %s:%d  %s<<%s>> LOPC %lld  New Polygon index %lld  E3dPolygon_Triangulate() failed. LNewPolygon->NumOfVertices %lld  POldPolygon->ITriangles_Count %lld%s\n", __FUNCTION__, __FILE__, __LINE__, ECodeRed, LPolyGroup->Name, (long long)LOPC, (long long)(LNewPolygon - PInfo->NewPolygons.Items), (long long)LNewPolygon->NumOfVertices, (long long)POldPolygon->ITriangles_Count, ECodeNormal);fflush(stdout);
	EIndex  LVI = 0;const E3dVertexNode*  LVertexNode = LNewPolygon->VertexNodes;for(;LVI < LNewPolygon->NumOfVertexNodes;LVI++)
	{
		const E3dVertexIndex  LVID = LVertexNode->VertexID;if(LVID >= 0)
		{
			const E3dVertex*  LVertex = EStruct_ByIndex(LDstMeshVertices, LDstMeshVertexSize, LVID);
			printf("    %s% 3lld%s : %f, %f, %f\n", ECodeCyan, (long long)LVID, ECodeNormal, XYZ_s(LVertex->Position));
		}
		else
		{
			printf("    Hole with %lld vertices:\n", -(long long)LVID);fflush(stdout);
		}

		EStruct_Inc(LVertexNode, LVertexNodeSize);
	}
//assert(0);
}

					if(_HighlightBadPolysInA) POldPolygon->Flags |= E3dPolygonSELECTED;
					LNewPolygon--;
					PInfo->NewPolygons.Count--;
				}
			}
			else
			{
printf("%s() %s:%d  %s<<%s>> LOPC %lld  New Polygon index %lld  LNewPolygon has < 3 Vertices. LNewPolygon->NumOfVertices %lld  POldPolygon->ITriangles_Count %lld%s\n", __FUNCTION__, __FILE__, __LINE__, ECodeRed, LPolyGroup->Name, (long long)LOPC, (long long)(LNewPolygon - PInfo->NewPolygons.Items), (long long)LNewPolygon->NumOfVertices, (long long)POldPolygon->ITriangles_Count, ECodeNormal);fflush(stdout);

Error:
				if(_HighlightBadPolysInA) POldPolygon->Flags |= E3dPolygonSELECTED;
				E3dPolygon_FreeContents(LNewPolygon);
				LNewPolygon--;
				PInfo->NewPolygons.Count--;
				LResult = E_CORRUPT_DATA;
			}
		}
	}




	EFree(LRelevantBNewVertices);

	return(LResult);
}


static inline EBool _AcceleratorValid(const E3dRWAccelerator* PRWAccelerator)
{
	if(PRWAccelerator->KDTree) return(TRUE);
	else if(PRWAccelerator->BVH) return(TRUE);
	else if(PRWAccelerator->UniformGrid.Voxels) return(TRUE);
	else
	{
		return(FALSE);
	}
}


//=======================================================================================
// To determine if a point is inside a Mesh:
// Check if it's inside the Mesh's BBox.
// NO : point is outside of Mesh
// YES:
// Generate a random unit vector.
// Shoot a ray in that direction.
// For simplicity, we just shoot a ray along a principal axis, e.g. +X.
// If the ray intersects an odd number of triangles, the point was inside the Mesh.
// Even hits: outside.
// If the winding orders are correct, we can just test the first hit.
// No hit, or front-face hit : the point is outside.
// Back-face hit : the point is inside
// We use an Accelerator (e.g. KD-tree, BVH etc., depending on how EQX is configured)
//=======================================================================================
static EBool E3d_PointInMesh(const E3dMesh* PMesh, E3dRWAccelerator* PRWAccelerator, const E3dCoord3* PPoint)
{
	E3dBBox  LBBox;

	if(PRWAccelerator->KDTree) LBBox = PRWAccelerator->KDTree->BBox;
	else if(PRWAccelerator->BVH) LBBox = PRWAccelerator->BVH->BBox;
	else if(PRWAccelerator->UniformGrid.Voxels) LBBox = PRWAccelerator->UniformGrid.BBox;
	else
	{
printf("%s() %s:%d  Mesh %s has no accelerator\n", __FUNCTION__, __FILE__, __LINE__, PMesh->Name);fflush(stdout);
		return(FALSE);
	}

	if(PPoint->X < LBBox.Min.X || PPoint->X > LBBox.Max.X) return(FALSE);
	if(PPoint->Y < LBBox.Min.Y || PPoint->Y > LBBox.Max.Y) return(FALSE);
	if(PPoint->Z < LBBox.Min.Z || PPoint->Z > LBBox.Max.Z) return(FALSE);

/*
	if(!E3d_Finite(PPoint->X) || !E3d_Finite(PPoint->Y) || !E3d_Finite(PPoint->Z))
	{
		assert(0);
	}
*/

//printf("%s() %s:%d  <%s> % 7.2f, % 7.2f, % 7.2f", __FUNCTION__, __FILE__, __LINE__, PMesh->Name, XYZ_p(PPoint));fflush(stdout);

	E3dGridRay  LRay;LRay.Origin = *PPoint;LRay.Direction = E3dCoord3_s(1.0, 0.0, 0.0);
	E3dRayHit  LHit;EClr_s(LHit);if(_RayIntersectScene(PRWAccelerator, &LRay, NULL,  &LHit) && LHit.RTriangle)
	{
		if(LRay.FrontFaceHit)
		{
//printf(" - FRONT FACE : OUT\n");fflush(stdout);
		return(FALSE);
	 }
//printf(" - %sIN%s\n", ECodeGreen, ECodeNormal);fflush(stdout);
//printf(" - %sIN%s", ECodeGreen, ECodeNormal);fflush(stdout);

#if E3dDEBUG
		if(_Verbose) { E3dPolygon*  LPolygon = (E3dPolygon*)(LRTriangle->Face);printf(" - %sIN%s hit back of Poly %p (%lld)", ECodeGreen, ECodeNormal, LPolygon, (long long)(LPolygon - PMesh->PolyGroups[0]->Polygons));fflush(stdout); }
#endif
		return(TRUE);
	}
	else
	{
//printf(" - NO HIT : OUT\n");fflush(stdout);
		return(FALSE);
	}
}


//========================================================================================================
// Intersect 2 E3dBEdges
//
// If the lines are parallel, the denominator |V1 X V2|^2 is 0
// If the lines are skew, s and t represent the parameters of the points at the closest approach
// Based on Ronald Goldman's page in Graphic Gems
// Line0(t) = P0 + V0 * t
// Line1(s) = P1 + V1 * s
//
// LT0 = Det{P1 - P0, V1, V0 X V1} / |V0 X V1|^2;
// s = Det{P1 - P0, V0, V0 X V1} / |V0 X V1|^2;
//========================================================================================================
static int _EdgesIntersect(const E3dBEdge* PEdge0, const E3dBEdge* PEdge1, E3dCoord3* PPoint, E3dCoordinate* PT)
{
	const E3dCoord3      LDir0 = PEdge0->Direction, LDir1 = PEdge1->Direction;
	const E3dCoord3      E_Sub3i_vv(LP1minusP0, PEdge1->Origin, PEdge0->Origin);
	const E_Cross3i_vv(LV0xV1, LDir0, LDir1);
	const E3dCoordinate  LV0xV1LengthSq = E_Dot3_vv(LV0xV1, LV0xV1);

	if(LV0xV1LengthSq < E3dEPSILON) return(0);	// Lines are parallel

	const E3dCoordinate  LT0 = E3d_Det3x3_vvv(LP1minusP0, LDir1, LV0xV1) / LV0xV1LengthSq;
	if(LT0 < 0.0 || LT0 > PEdge0->Length) return(0);

	const E3dCoordinate  LT1 = E3d_Det3x3_vvv(LP1minusP0, LDir0, LV0xV1) / LV0xV1LengthSq;
	if(LT1 < 0.0 || LT1 > PEdge1->Length) return(0);

	E3dCoord3  LP0;E_AddScaled3_vvv(PEdge0->Origin, LDir0, LT0, LP0);
	E3dCoord3  LP1;E_AddScaled3_vvv(PEdge1->Origin, LDir1, LT1, LP1);

	const E3dCoord3  E_Sub3i_vv(LP0P1, LP1, LP0);const E3dCoordinate  LDistSq = E_Dot3_vv(LP0P1, LP0P1);

//printf("%s() %s:%d  t0 %f  t1 %f  LDistSq %f\n", __FUNCTION__, __FILE__, __LINE__, LT0, LT1, LDistSq);fflush(stdout);

	if(LDistSq < E3dEPSILON * E3dEPSILON) { *PPoint = LP0;*PT = LT0;return(1); }	// Intersection found

	return(0);
}


//========================================================================================================
// Intersect Edge with all Polygons of Mesh1 and add the resulting points as new Vertices to PDstMesh
// Use Accelerator for ray-tracing step by step until the end of the Edge.
//========================================================================================================
static EIndex _EdgePolysIntersect(E3dInfo* PInfo, const uint8_t PMode, const EIndex PEdgeIndex, const E3dEdge* PEdge, E3dBEdge* PBEdge, const E3dBVertex* PVertices0, E3dBPolygon** PBPolygonLists,  E3dRWAccelerator* PRWAccelerator)
{
	const E3dBVertex*  LStartVtx = PVertices0 + PEdge->Start;
	const E3dBVertex*  LEndVtx = PVertices0 + PEdge->End;

	E3dCoord3      E_Sub3i_vv(LDir, LEndVtx->Position, LStartVtx->Position);
	E3dGridRay     LRay;LRay.Origin = LStartVtx->Position;
	E3dCoordinate  LEdgeLength;E3d_NormalizeGetLength3_v(LDir, LEdgeLength);
	LRay.Direction = LDir;

	E3dCoordinate  LRemainingEdgeLength = LEdgeLength;
	EIndex         LTotalIntersections = 0;
	void*          LExcludeFace = NULL;
	do
	{
		E3dRayHit  LHit;EClr_s(LHit);if(_RayIntersectScene(PRWAccelerator, &LRay, LExcludeFace,  &LHit) && LHit.RTriangle)
		{
			const E3dCoordinate  LT = LHit.T;
//printf("%s() %s:%d  %f %f\n", __FUNCTION__, __FILE__, __LINE__, LT, LRemainingEdgeLength);fflush(stdout);
			if(LT <= LRemainingEdgeLength)
			{
				const E3dCoord3  LPosition = E3dCoord3_s(LRay.Origin.X + LDir.X * LT, LRay.Origin.Y + LDir.Y * LT, LRay.Origin.Z + LDir.Z * LT);

				const E3dRTriangle*  LRTriangle = LHit.RTriangle;
				LExcludeFace = LRTriangle->Face;

				if(PBEdge)
				{
					const E3dPolygon*  LHitPolygon = (E3dPolygon*)LRTriangle->Face;

// Check if this Polygon is in the EdgeEdgePolygons list. If so, ignore hit, as it was already registered as an Edge-Edge intersection.
//
					E3dPolygon**  LEdgeEdgePolygons = PBEdge->EdgeEdgePolygons;

//printf("%s() %s:%d %sEdgeEdgePoly check Edge %lld %lld%s\n", __FUNCTION__, __FILE__, __LINE__, ECodeYellow, (long long)PEdgeIndex, (long long)PBEdge->EdgeEdgePolygons_Count, ECodeNormal);fflush(stdout);

					EIndex  LPI = 0;const EIndex  LPN = PBEdge->EdgeEdgePolygons_Count;for(;LPI < LPN;LPI++)
					{
printf("%s() %s:%d %s%p ~ %p%s", __FUNCTION__, __FILE__, __LINE__, ECodeYellow,  LHitPolygon, LEdgeEdgePolygons[LPI],  ECodeNormal);fflush(stdout);
						if(LEdgeEdgePolygons[LPI] == LHitPolygon)
						{
printf(" - %sFOUND%s\n", ECodeYellow, ECodeNormal);fflush(stdout);
							goto Next;
						}
printf("\n");fflush(stdout);
					}
//				if(LPI == LPN) continue;  // Polygon found, continue main do/while loop

					E3dBEdgeVertex*  LBEdgeVertex = EList_StructAddA_s(PBEdge->NewVertices, 4);
					LBEdgeVertex->Position = LPosition;
					LBEdgeVertex->EdgeT = LT;
					LBEdgeVertex->IndexInMesh = -1;  // This will be set in the BNewVertices "gather phase"
					LBEdgeVertex->State = LRay.FrontFaceHit ? E3dENTER : E3dEXIT;
					LBEdgeVertex->Mode = PMode;
					LBEdgeVertex->BEdgeIndex = -1;  // This is an Edge-Polygon (not an Edge-Edge) intersection, so set this to -1
					LBEdgeVertex->RTriangle = LRTriangle;LBEdgeVertex->UV = LHit.UV;LBEdgeVertex->Face = LRTriangle->Face;
				}
				else
				{
printf("%s() %s:%d %sEdgeEdgePoly check%s\n", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, ECodeNormal);fflush(stdout);
				}

				if(PBPolygonLists)
				{
					const E3dPolygon*        LPolygon = (E3dPolygon*)LExcludeFace;
					E3dRPolyGroup*           LRPolyGroup = LRTriangle->RPolyGroup;
					const EIndex             LPolyGroupIndexInMesh = LRPolyGroup->PolyGroupIndexInMesh;
					const E3dPolyGroup*      LPolyGroup = LRPolyGroup->PolyGroup;
//				const E3dVertexNodeSize  LVertexNodeSize = LPolyGroup->VertexNodeSize;
					const EIndex             LPolygonIndexInPolyGroup = LPolygon - LPolyGroup->Polygons.Items;
					E3dBPolygon*             LBPolygon = PBPolygonLists[LPolyGroupIndexInMesh] + LPolygonIndexInPolyGroup;

					if(!LBPolygon->SurfaceTrimmed) LBPolygon->SurfaceTrimmed = TRUE;
				}

				LTotalIntersections++;

Next:
				LRemainingEdgeLength -= LT;
				LRay.Origin = LPosition;
			}
			else break;
		}
		else break;
	} while(1);

	return(LTotalIntersections);
}


static void _BPolygonFree(E3dBPolygon* PBPolygon)
{
	EFreeChk(PBPolygon->EdgeIndices);
}


static inline void E3d_BEdgeGetBBox(const E3dBEdge* PBEdge, E3dBBox* PBBox)
{
	PBBox->Min.X = E_Min(PBEdge->Origin.X, PBEdge->EndPoint.X);
	PBBox->Min.Y = E_Min(PBEdge->Origin.Y, PBEdge->EndPoint.Y);
	PBBox->Min.Z = E_Min(PBEdge->Origin.Z, PBEdge->EndPoint.Z);
	PBBox->Max.X = E_Min(PBEdge->Origin.X, PBEdge->EndPoint.X);
	PBBox->Max.Y = E_Min(PBEdge->Origin.Y, PBEdge->EndPoint.Y);
	PBBox->Max.Z = E_Min(PBEdge->Origin.Z, PBEdge->EndPoint.Z);
}


#if EDGE_EDGE_INTERSECT
//========================================================================================
// Build KDTree for an Accelerator
//========================================================================================
static E3dKDTree* _BEdgesBuildKDTree(const E3dBEdge* PBEdges, const EIndex PBEdges_Count)
{
	E3dBBox*  LAllBounds;ENewNClr(PBEdges_Count, LAllBounds);
	E3dBBox   LTreeBBox;

#if PlTIMING == 2
ETime  LPrev = E_GetMicroseconds();
#endif

 {
	 int  LI = 0;E3dBBox*  LBBox = LAllBounds;for(;LI < PBEdges_Count;LI++, LBBox++)
	 {
		 E3d_BEdgeGetBBox(PBEdges + LI, LBBox);

// Compute tree BBox
//
		 if(LI) E3dBBox_2b(LAllBounds + LI, &LTreeBBox);
		 else LTreeBBox = LAllBounds[LI];
	 }
 }

#if PlTIMING == 2
{ const ETime  LNow = E_GetMicroseconds();char  s[256];printf(" %s() Edge BBoxes took %s\n", __FUNCTION__, EStr_MicrosecondsToString((LNow - LPrev), s, FALSE));fflush(stdout);LPrev = LNow; }
#endif

 E3dKDTree*  LTree = E3dKDTree_Build((const void**)PBEdges, PBEdges_Count, LAllBounds, LTreeBBox);

#if PlTIMING == 2
{ const ETime  LNow = E_GetMicroseconds();char  s[256];printf(" %s() Edge KDTree took %s\n", __FUNCTION__, EStr_MicrosecondsToString((LNow - LPrev), s, FALSE));fflush(stdout);LPrev = LNow; }
#endif

	EFree(LAllBounds);LTree->AllBounds = NULL;

	return(LTree);
}
#endif


//================================================
// Comparison function for sorting EdgeVertices
//================================================
static int _EdgeVertexCmp(const void* P0, const void* P1)
{
	const E3dBEdgeVertex*  L0 = (E3dBEdgeVertex*)P0;const E3dCoordinate  LT0 = L0->EdgeT;
	const E3dBEdgeVertex*  L1 = (E3dBEdgeVertex*)P1;const E3dCoordinate  LT1 = L1->EdgeT;

	if(LT0 < LT1) return(-1);
	else if(LT0 > LT1) return(1);
	else return(0);
}


//========================================================
// Select Edges that intersect between Mesh0 and Mesh1
//========================================================
static EIndex _EdgeEdgeIntersectShow(const E3dMesh* PMesh0, E3dBEdge* P0BEdges,  const E3dMesh* PMesh1, E3dBEdge* P1BEdges,  E3dInfo* PInfo)
{
	E3dEdge*              LMesh0Edges = PMesh0->Edges.Items;
	E3dEdge*              LMesh1Edges = PMesh1->Edges.Items;
	const EIndex          LMesh0EN = PMesh0->Edges.Count;
	const EIndex          LMesh1EN = PMesh1->Edges.Count;
	EIndex                LIntersections = 0;

//E3dKDTree*            LMesh0EdgesKDTree = _BEdgesBuildKDTree(P0BEdges, PMesh0->Edges.Count);
//E3dKDTree*            LMesh1EdgesKDTree = _BEdgesBuildKDTree(P1BEdges, PMesh1->Edges.Count);

	{
		EIndex  L0EC = 0;E3dBEdge*  L0BEdge = P0BEdges;for(;L0EC < LMesh0EN;L0EC++, L0BEdge++)
		{
			EIndex  L1EC = 0;E3dBEdge*  L1BEdge = P1BEdges;for(;L1EC < LMesh1EN;L1EC++, L1BEdge++)
			{
				E3dCoord3  LPosition;E3dCoordinate  LT;if(_EdgesIntersect(L0BEdge, L1BEdge, &LPosition, &LT))
				{
					E3dEdge*  LMesh0Edge = LMesh0Edges + L0EC;
					E3dEdge*  LMesh1Edge = LMesh1Edges + L1EC;

					LMesh0Edge->Flags |= E3dEdgeSELECTED;
					LMesh1Edge->Flags |= E3dEdgeSELECTED;

					LIntersections++;

#if E3dDEBUG
printf("%s() %s:%d  Mesh0 Edge %ll3d and Mesh1 Edge %ll3d intersect at (% 6.3f % 6.3f % 6.3f) (t = %f)\n", __FUNCTION__, __FILE__, __LINE__, (long long)L0EC, (long long)L1EC, XYZ_s(LPosition), LT);fflush(stdout);
#endif
				}
			}
		}
	}

//	E_DeleteCkh(LMesh0EdgesKDTree);
//	E_DeleteCkh(LMesh1EdgesKDTree);

	return(LIntersections);
}


#if EDGE_EDGE_INTERSECT
//================================================================================================
// Stage : Edge-Edge intersection
// Add Polygons from the Edge on the other Mesh to PBEdge->EdgeEdgePolygons.
// This is to eliminate those Polygons from the Edge-Polygin intersection tests later on.
// Also, mark Polygons on the current Mesh's Edge, that they are to be trimmed.
//================================================================================================
static void _EdgeEdgeAddPoly(const E3dMesh* PMesh, E3dBEdge* PBEdge, const E3dWingedEdge* PWingedEdge, E3dBPolygon** PBPolygonLists,  const E3dWingedEdge* PMeshBWingedEdge)
{
	EIndex  LPI = 0;const EIndex  LPN = PMeshBWingedEdge->Polygons_Count;const E3dEdgePolygonInfo*  LEdgePolygonInfo = PMeshBWingedEdge->Polygons;for(;LPI < LPN;LPI++, LEdgePolygonInfo++)
	{
		{
			const E3dPolygon*  LPolygon   = LEdgePolygonInfo->Polygon;
			EList_PointerAddAChk(PBEdge->EdgeEdgePolygons, 4, LPolygon);
		}


		EIndex  LBPC = 0;const EIndex  LBPN = PWingedEdge->Polygons_Count;const E3dEdgePolygonInfo*  LBEdgePolygonInfo = PWingedEdge->Polygons;for(;LBPC < LBPN;LBPC++, LBEdgePolygonInfo++)
		{
			const E3dPolygon*    LPolygon   = LBEdgePolygonInfo->Polygon;
			const E3dPolyGroup*  LPolyGroup = PMesh->PolyGroups[LBEdgePolygonInfo->PolyGroupIndex];

			const EIndex  LPolyGroupIndexInMesh = LPolyGroup->TmpIndex0, LPolygonIndexInPolyGroup = LPolygon - LPolyGroup->Polygons;

			E3dBPolygon*  LBPolygon = PBPolygonLists[LPolyGroupIndexInMesh] + LPolygonIndexInPolyGroup;

			if(!LBPolygon->SurfaceTrimmed)
			{
// Allocate Edge list for Polygon. It will be filled in later because it's more efficient to fill it in walking the Edges than here, trying to find the Edges. See (C).
//
				if(LBPolygon->EdgeIndices == NULL) ENewNClr(LPolygon->NumOfVertexNodes, LBPolygon->EdgeIndices);

				LBPolygon->SurfaceTrimmed = TRUE;
			}
		}

	}
}


static void _EdgeEdgeIntersect(const E3dMesh* PMesh0, E3dBEdge* P0BEdges, E3dBPolygon** P0BPolygons,  const E3dMesh* PMesh1, E3dBEdge* P1BEdges, E3dBPolygon** P1BPolygons,  E3dInfo* PInfo,  E3dMesh* PDstMesh)
{
	const E3dWingedEdge*  LMesh0WingedEdges = PMesh0->WingedEdges;
	const E3dWingedEdge*  LMesh1WingedEdges = PMesh1->WingedEdges;
	const EIndex          LMesh0EN = PMesh0->Edges.Count;
	const EIndex          LMesh1EN = PMesh1->Edges.Count;

//	E3dKDTree*            LMesh0EdgesKDTree = _BEdgesBuildKDTree(P0BEdges, PMesh0->Edges.Count);
//	E3dKDTree*            LMesh1EdgesKDTree = _BEdgesBuildKDTree(P1BEdges, PMesh1->Edges.Count);


#if PlTIMING == 2
	ETime  LPrev = E_GetMicroseconds();
#endif

	{
		EIndex  L0EC = 0;E3dBEdge*  L0BEdge = P0BEdges;for(;L0EC < LMesh0EN;L0EC++, L0BEdge++)
		{
//printf("%s() %s:%d  %sE%lld % 6.3f  % 6.3f  % 6.3f->% 6.3f  % 6.3f  % 6.3f%s  vs. : ", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, (long long)L0EC, XYZ_s(L0BEdge->Origin), XYZ_s(L0BEdge->Direction), ECodeNormal);fflush(stdout);
			EIndex  L1EC = 0;E3dBEdge*  L1BEdge = P1BEdges;for(;L1EC < LMesh1EN;L1EC++, L1BEdge++)
			{
//printf(" E%lld [% 6.3f  % 6.3f  % 6.3f->% 6.3f  % 6.3f  % 6.3f]", (long long)L1EC, XYZ_s(L1BEdge->Origin), XYZ_s(L1BEdge->Direction));fflush(stdout);

				E3dCoord3  LPosition;E3dCoordinate  LT;if(_EdgesIntersect(L0BEdge, L1BEdge, &LPosition, &LT))
				{
//printf("%s() %s:%d  Mesh0 Edge %lld [% 6.3f  % 6.3f  % 6.3f->% 6.3f  % 6.3f  % 6.3f] and Mesh1 Edge %lld [% 6.3f  % 6.3f  % 6.3f->% 6.3f  % 6.3f  % 6.3f] intersect at  % 6.3f  % 6.3f  % 6.3f (t = %f)\n", __FUNCTION__, __FILE__, __LINE__,  (long long)L0EC, XYZ_s(L0BEdge->Origin), XYZ_s(L0BEdge->Direction),  (long long)L1EC, XYZ_s(L1BEdge->Origin), XYZ_s(L1BEdge->Direction),  XYZ_s(LPosition), LT);fflush(stdout);

#if E3dDEBUG
printf("%s() %s:%d  Mesh0 Edge %3lld and Mesh1 Edge %3lld intersect at (% 6.3f % 6.3f % 6.3f) (t = %f)\n", __FUNCTION__, __FILE__, __LINE__, (long long)L0EC, (long long)L1EC, XYZ_s(LPosition), LT);fflush(stdout);
#endif


					_EdgeEdgeAddPoly(PMesh0, L0BEdge, LMesh0WingedEdges + L0EC, P0BPolygons,  LMesh1WingedEdges + L1EC);
					_EdgeEdgeAddPoly(PMesh1, L1BEdge, LMesh1WingedEdges + L1EC, P1BPolygons,  LMesh0WingedEdges + L0EC);


					{
						E3dBEdgeVertex*  LBEdgeVertex = EList_StructAdd(L0BEdge->NewVertices);L0BEdge->NumOfEdgeEdgeIntersections += 1;
//					LBEdgeVertex->IndexInMesh = PDstMesh->Vertices.Count + PInfo->BNewVertices_Count;
						LBEdgeVertex->IndexInMesh = PInfo->BNewVertices_Count;
						LBEdgeVertex->State = E3dON_EDGE;  // This will be set to E3dENTER/E3dEXIT etc., based on whether the Edge only cuts into this Edge, or this Edge lies on the Polygon on the other Mesh etc...
						LBEdgeVertex->Position = LPosition;
						LBEdgeVertex->Face = NULL;
						LBEdgeVertex->BEdgeIndex = L1EC;
					}

					{
						E3dBEdgeVertex*  LBEdgeVertex = EList_StructAdd(L1BEdge->NewVertices);L1BEdge->NumOfEdgeEdgeIntersections += 1;
//					LBEdgeVertex->IndexInMesh = PDstMesh->Vertices.Count + PInfo->BNewVertices_Count;
						LBEdgeVertex->IndexInMesh = PInfo->BNewVertices_Count;
						LBEdgeVertex->State = E3dON_EDGE;  // This will be set to E3dENTER/E3dEXIT etc., based on whether the Edge only cuts into this Edge, or this Edge lies on the Polygon on the other Mesh etc...
						LBEdgeVertex->Position = LPosition;
						LBEdgeVertex->Face = NULL;
						LBEdgeVertex->BEdgeIndex = L0EC;
					}

					{
						E3dBNewVertex*  LBNewVertex = EList_StructAddA(PInfo->BNewVertices, 64);if(LBNewVertex)
						{
							LBNewVertex->Position = LPosition;
//						LBNewVertex->IndexInMesh = PDstMesh->Vertices.Count + PInfo->BNewVertices_Count - 1;
							LBNewVertex->IndexInMesh = PInfo->BNewVertices_Count - 1;
							LBNewVertex->Mode = E3dON_MESHA_EDGE_MESHB_EDGE;
							LBNewVertex->EdgeIndex = L1EC;
							LBNewVertex->EdgeT = LT;

							LBNewVertex->RTriangle = NULL;LBNewVertex->Face = NULL;
							LBNewVertex->UV = E3dCoord2_s(0.0, 0.0);
						}
					}

				}
			}
//printf("\n");fflush(stdout);
		}
	}

#if PlTIMING == 2
{ ETime  LNow = E_GetMicroseconds();char  s[256];printf("%s() %s:%d  Edge-Edge intersections : %s\n", __FUNCTION__, __FILE__, __LINE__, EStr_MicrosecondsToString((LNow - LPrev), s, FALSE));fflush(stdout);LPrev = LNow; }
#endif

//	E_DeleteCkh(LMesh0EdgesKDTree);
//	E_DeleteCkh(LMesh1EdgesKDTree);
}
#endif


static void _BEdgesFree(E3dBEdge* PBEdges, const EIndex PCount)
{
	EIndex  LEC = 0;E3dBEdge*  LBEdge = PBEdges;for(;LEC < PCount;LEC++, LBEdge++)
	{
		EFreeChk(LBEdge->EdgeEdgePolygons);
		EList_FreeAChk_s(LBEdge->NewVertices);
	}

	EFree(PBEdges);
}


#ifdef E3dDebugADDMESHBPOLYS
static void _BPolygonsFree(E3dBPolygon** PBPolygonLists, const EIndex PCount, const E3dMesh* PMesh)
#else
static void _BPolygonsFree(E3dBPolygon** PBPolygonLists, const E3dMesh* PMesh)
#endif
{
	if(PBPolygonLists)
	{
#ifdef E3dDebugADDMESHBPOLYS
		EIndex  LPGI = 0;for(;LPGI < PCount;LPGI++)
#else
		EIndex  LPGI = 0;const EIndex  LPGN = PMesh->PolyGroups.Count;for(;LPGI < LPGN;LPGI++)
#endif
		{
			E3dBPolygon*  LBPolygon = PBPolygonLists[LPGI];
			EIndex  LPI = 0;const EIndex  LPN = PMesh->PolyGroups.Items[LPGI]->Polygons.Count;for(;LPI < LPN;LPI++, LBPolygon++) _BPolygonFree(LBPolygon);
			EFreeChk(PBPolygonLists[LPGI]);
		}

		EFreeChk(PBPolygonLists);
	}
}


//================================================================================================
// - Sort new Vertices on Edges, by T, if there were Edge-Edge intersections on that Edge
// - Figure out in/out state for those points
// - Remove Vertices that don't trigger in/out state changes
//================================================================================================
static void _EdgesCheck(const E3dMesh* PMesh, E3dBEdge* PBEdges, const E3dBVertex* PBVertices)
{
	const EIndex  LEN = PMesh->Edges.Count;
	EIndex        LQSorts = 0;

#if PlTIMING == 2
	ETime         LPrev = E_GetMicroseconds();
#endif

	EIndex  LEC = 0;const E3dEdge*  LEdge = PMesh->Edges.Items;E3dBEdge*  LBEdge = PBEdges;for(;LEC < LEN;LEC++, LEdge++, LBEdge++)
	{
		if(LBEdge->NumOfEdgeEdgeIntersections)
		{
			qsort(LBEdge->NewVertices.Items, LBEdge->NewVertices.Count, sizeof(E3dBEdgeVertex), _EdgeVertexCmp);
			LQSorts++;

// Walk through new Vertices on LBEdge and set their State / delete the ones not needed.
// E3dFINISHME
// This is a dumb test at the moment...
//
			const E3dBVertex*  LBVertex = PBVertices + LEdge->Start;
			char  LState = LBVertex->State;
			EIndex  LI = 0;for(;LI < LBEdge->NewVertices.Count;)
			{
				E3dBEdgeVertex*  LBEdgeVertex = LBEdge->NewVertices.Items + LI;
				if(LBEdgeVertex->State == E3dON_EDGE)
				{
					if(LState == E3dINSIDE)
					{
						LBEdgeVertex->State = E3dEXIT;
						LState = 0;
					}
					else
					{
						LBEdgeVertex->State = E3dENTER;
						LState = E3dINSIDE;
					}
				}
				LI++;
			}
		}
	}

#if PlTIMING == 2
{ ETime  LNow = E_GetMicroseconds();char  s[256];printf("%s() %s:%d  Edge-Edge qsorts : %lld  %s\n", __FUNCTION__, __FILE__, __LINE__, (long long)LQSorts, EStr_MicrosecondsToString((LNow - LPrev), s, FALSE));fflush(stdout);LPrev = LNow; }
#endif
}


static void _MeshSetPolygonEdgeIndices(const E3dMesh* PMesh, E3dBPolygon** PBPolygonLists)
{
	EIndex  LEC = 0;const EIndex  LEN = PMesh->Edges.Count;const E3dEdge*  LEdge = PMesh->Edges.Items;const E3dWingedEdge*  LWingedEdge = PMesh->WingedEdges;for(;LEC < LEN;LEC++, LEdge++, LWingedEdge++)
	{
//printf("%s() %s:%d  Edge %lld (Vtx %lld - %lld)\n", __FUNCTION__, __FILE__, __LINE__, (long long)LEC, (long long)LEdge->Start, (long long)LEdge->End);fflush(stdout);
		EIndex  LPI = 0;const EIndex  LPN = LWingedEdge->Polygons_Count;const E3dEdgePolygonInfo*  LEdgePolygon = LWingedEdge->Polygons;for(;LPI < LPN;LPI++, LEdgePolygon++)
		{
			const E3dPolyGroup*       LPolyGroup = PMesh->PolyGroups.Items[LEdgePolygon->PolyGroupIndex];
			const E3dVertexNodeSize   LVertexNodeSize = LPolyGroup->VertexNodeSize;
			const EIndex              LPolyGroupIndex = LPolyGroup->TmpIndex0;
			const E3dPolygon*         LPolygon = LEdgePolygon->Polygon;
			const EIndex              LPolygonIndex = LPolygon - LPolyGroup->Polygons.Items;
			E3dBPolygon*              LBPolygon = PBPolygonLists[LPolyGroupIndex] + LPolygonIndex;
			if(LBPolygon->EdgeTrimmed)
			{
				if(LBPolygon->EdgeIndices == NULL) ENewNClr(LPolygon->NumOfVertexNodes, LBPolygon->EdgeIndices);

				const E3dVertexNode*  LVertexNodes = LPolygon->VertexNodes;
//printf(" %s() %s:%d Poly %d   %d - %d\n", __FUNCTION__, __FILE__, __LINE__, LPolygonIndex, LEdgePolygon->StartVertexNodeIndex, LEdgePolygon->EndVertexNodeIndex);fflush(stdout);

				EIndex  LStartVertexNodeIndex = LEdgePolygon->StartVertexNodeIndex;
				EIndex  LEndVertexNodeIndex = LEdgePolygon->EndVertexNodeIndex;
				EIndex  LIndex;

// Check if this is the last Edge in the Polygon contour
//
				{
					if(E3dVertexNode_IndexIsContourStart(LVertexNodes, LVertexNodeSize, LStartVertexNodeIndex))
					{
						if(LEndVertexNodeIndex == LStartVertexNodeIndex + 1) LIndex = LStartVertexNodeIndex;
						else LIndex = LEndVertexNodeIndex;
					}
					else if(E3dVertexNode_IndexIsContourStart(LVertexNodes, LVertexNodeSize, LEndVertexNodeIndex))
					{
						if(LStartVertexNodeIndex == LEndVertexNodeIndex + 1) LIndex = LEndVertexNodeIndex;
						else LIndex = LStartVertexNodeIndex;
					}
					else LIndex = LStartVertexNodeIndex < LEndVertexNodeIndex ? LStartVertexNodeIndex : LEndVertexNodeIndex;
				}

//printf(" %s() %s:%d  Poly %lld  Edge %lld\n", __FUNCTION__, __FILE__, __LINE__, (long long)LPolygonIndex, (long long)LIndex);fflush(stdout);
				LBPolygon->EdgeIndices[LIndex] = LEC;
			}
		}
	}
}


static void _OffsetVertices(E3dInfo* PInfo, const EIndex PVIndexOffset,  const E3dMesh* PMesh, E3dBEdge* PBEdges, const EIndex PEIndexOffset)
{
tm_PUSH(__FUNCTION__);
	{
		EIndex  LVI = 0;const EIndex  LVN = PInfo->BNewVertices_Count;E3dBNewVertex*  LBNewVertex = PInfo->BNewVertices;for(;LVI < LVN;LVI++, LBNewVertex++)
		{
			LBNewVertex->IndexInMesh += PVIndexOffset;
		}
	}

	{
		EIndex  LEC = 0;const EIndex  LEN = PMesh->Edges.Count;E3dBEdge*  LBEdge = PBEdges;for(;LEC < LEN;LEC++, LBEdge++)
		{
			EIndex  LVI = 0;const EIndex  LVN = LBEdge->NewVertices.Count;E3dBEdgeVertex*  LBEdgeVertex = LBEdge->NewVertices.Items;for(;LVI < LVN;LVI++, LBEdgeVertex++)
			{
				LBEdgeVertex->IndexInMesh += PEIndexOffset;
			}
		}
	}
tm_POP();
}


typedef struct
{
	EListA(, E3dEdge, DstMeshEdges);

	E3dVertexLinkNew*  VertexLinks;
	EIndex             VertexLinksStride;
} E3dEdgeTransferStruct;



#define DE3d_EdgeSetVertices(Edge,  p0, p1) do\
{\
	const EIndex  lM0 = PSrcMeshToDstMeshVertexIndexTable[p0], lM1 = PSrcMeshToDstMeshVertexIndexTable[p1];\
	EIndex        lMM0 = lM0, lMM1 = lM1;\
\
	if(lM0 == -1)\
	{\
		_DstMeshCheckVertexEdge(PSrcMesh, p0,  PSrcMeshToDstMeshVertexIndexTable, PSrcMesh_to_DstMeshMatrix,  PDstMesh, LVertexNode);\
		lMM0 = PSrcMeshToDstMeshVertexIndexTable[p0];\
	}\
	if(lM1 == -1)\
	{\
		_DstMeshCheckVertexEdge(PSrcMesh, p1,  PSrcMeshToDstMeshVertexIndexTable, PSrcMesh_to_DstMeshMatrix,  PDstMesh, LVertexNode);\
		lMM1 = PSrcMeshToDstMeshVertexIndexTable[p1];\
	}\
if(lM0 == -1 || lM1 == -1) { printf("%s() %s:%d   %sCorrected%s  Orig %lld - %lld  from %s%lld - %lld%s to %lld - %lld\n", __FUNCTION__, __FILE__, __LINE__, ECodeYellow, ECodeNormal, (long long)(p0), (long long)(p1), ECodeYellow, (long long)lM0, (long long)lM1, ECodeNormal,  (long long)lMM0, (long long)lMM1);fflush(stdout); }\
/*
assert(lMM0 >= 0);\
assert(lMM1 >= 0);*/\
	E3dEdge_SetVertices(Edge,  lMM0, lMM1);\
} while(0)


static inline void _TransferEdges(const E3dMesh* PSrcMesh, const E3dBVertex* PBVertices, const E3dBEdge* PBEdges, const uint8_t PState, EIndex* PSrcMeshToDstMeshVertexIndexTable, const EBool PDoSlicedEdges,  E3dEdgeTransferStruct* PEdgeTransferStruct, const E3dCoordinate* PSrcMesh_to_DstMeshMatrix,  E3dMesh* PDstMesh)
{
	const E3dVertexSize  LVertexCoreSize = E3dVertexCore_Size();
	EIndex  LEC = 0;const EIndex  LEN = PSrcMesh->Edges.Count;const E3dEdge*  LEdge = PSrcMesh->Edges.Items;const E3dBEdge*  LBEdge = PBEdges;for(;LEC < LEN;LEC++, LEdge++, LBEdge++)
	{
		const E3dBVertex*  LBVertex = PBVertices + LEdge->Start;

		const EIndex  LVN = LBEdge->NewVertices.Count;if(LVN > 0)
		{
// Edge was sliced up
//
			if(PDoSlicedEdges)
			{
				uint8_t  LInOutState = LBVertex->State;const E3dBEdgeVertex*  LNewVertex = LBEdge->NewVertices.Items;

// Start - First NewVertex
//
				{
					if(LInOutState == PState)
					{
#if E3dDEBUG
						if(PSrcMeshToDstMeshVertexIndexTable && PSrcMeshToDstMeshVertexIndexTable[LEdge->Start] < 0)
						{
printf("%s() %s:%d  <%s> %sLEdge->Start %lld has no equivalent in PDstMesh%s\n", __FUNCTION__, __FILE__, __LINE__, PSrcMesh->Name, ECodeRed, (long long)LEdge->Start, ECodeNormal);fflush(stdout);
						}
						else
#endif
						{
							E3dEdge*  LNewEdge = EList_StructAddA(PEdgeTransferStruct->DstMeshEdges, 1024);if(LNewEdge)
							{
								E3dEdge_Copy(LEdge, LNewEdge);
								if(PSrcMeshToDstMeshVertexIndexTable) DE3d_EdgeSetVertices(LNewEdge,  LEdge->Start, LNewVertex->IndexInMesh);
								else E3dEdge_SetVertices(LNewEdge,  LEdge->Start, LNewVertex->IndexInMesh);
							}
						}
					}
				}

				if(LNewVertex->State == E3dENTER) LInOutState = E3dINSIDE;
				else if(LNewVertex->State == E3dEXIT) LInOutState = E3dOUTSIDE;

// NewVertices loop
//
				EIndex  LVI = 1;const E3dBEdgeVertex*  LNextNewVertex = LBEdge->NewVertices.Items + 1;for(;LVI < LVN;LVI++, LNewVertex++, LNextNewVertex++)
				{
#if E3dDEBUG
printf("%s() %s:%d  <%s> LVI %lld\n", __FUNCTION__, __FILE__, __LINE__, PSrcMesh->Name, (long long)LVI);fflush(stdout);
#endif
					if(LInOutState == PState)
					{
						E3dEdge*  LNewEdge = EList_StructAddA(PEdgeTransferStruct->DstMeshEdges, 1024);if(LNewEdge)
						{
							E3dEdge_Copy(LEdge, LNewEdge);
							if(PSrcMeshToDstMeshVertexIndexTable) DE3d_EdgeSetVertices(LNewEdge,  LNewVertex->IndexInMesh, LNextNewVertex->IndexInMesh);
							else E3dEdge_SetVertices(LNewEdge,  LNewVertex->IndexInMesh, LNextNewVertex->IndexInMesh);
						}
					}
					if(LNextNewVertex->State == E3dENTER) LInOutState = E3dINSIDE;
					else if(LNextNewVertex->State == E3dEXIT) LInOutState = E3dOUTSIDE;
				}


// Last NewVertex - LEdge->End
//
				{
					if(LInOutState == PState)
					{
#if E3dDEBUG
						if(PSrcMeshToDstMeshVertexIndexTable && PSrcMeshToDstMeshVertexIndexTable[LEdge->End] < 0)
						{
printf("%s() %s:%d  <%s> %sLEdge->End %lld has no equivalent in PDstMesh%s\n", __FUNCTION__, __FILE__, __LINE__, PSrcMesh->Name, ECodeRed, (long long)LEdge->End, ECodeNormal);fflush(stdout);
						}
						else
#endif
						{
							E3dEdge*  LNewEdge = EList_StructAddA(PEdgeTransferStruct->DstMeshEdges, 1024);if(LNewEdge)
							{
								E3dEdge_Copy(LEdge, LNewEdge);
								if(PSrcMeshToDstMeshVertexIndexTable) DE3d_EdgeSetVertices(LNewEdge,  LEdge->End, LNewVertex->IndexInMesh);
								else E3dEdge_SetVertices(LNewEdge,  LEdge->End, LNewVertex->IndexInMesh);
							}
						}
					}
				}
			}
		}
		else
		{
// Edge was not intersected, so it's sufficient to check one of its Vertices (Start/End are either both inside or both outside the other Mesh)
// Check if it is used in PDstMesh
//
//printf("%s() %s:%d  LEdge->Start %5lld  LEdge->End %5lld", __FUNCTION__, __FILE__, __LINE__, (long long)LEdge->Start, (long long)LEdge->End);fflush(stdout);

			if(LBVertex->State == PState)
			{
//printf(" - State:%d", LBVertex->State);fflush(stdout);

#if E3dDEBUG
if(PSrcMeshToDstMeshVertexIndexTable)
{
	if(PSrcMeshToDstMeshVertexIndexTable[LEdge->Start] < 0)
	{
		printf("%s() %s:%d  <%s> %sStart / End %lld has no equivalent in PDstMesh%s\n", __FUNCTION__, __FILE__, __LINE__, PSrcMesh->Name, ECodeRed, (long long)LEdge->Start, ECodeNormal);fflush(stdout);
	}

	if(PSrcMeshToDstMeshVertexIndexTable[LEdge->End] < 0)
	{
		printf("%s() %s:%d  <%s> %sEnd / End %lld has no equivalent in PDstMesh%s\n", __FUNCTION__, __FILE__, __LINE__, PSrcMesh->Name, ECodeRed, (long long)LEdge->End, ECodeNormal);fflush(stdout);
	}
}
#endif

				E3dEdge*  LNewEdge = EList_StructAddA(PEdgeTransferStruct->DstMeshEdges, 1024);if(LNewEdge)
				{
					E3dEdge_Copy(LEdge, LNewEdge);
					if(PSrcMeshToDstMeshVertexIndexTable)
					{
						DE3d_EdgeSetVertices(LNewEdge,  LNewEdge->Start, LNewEdge->End);
					}
// Mesh0's Vertices were copied 1 to 1 into PDstMesh (they will be pruned later), so we don't need to remap Start and End
				}
			}
//printf("\n");fflush(stdout);
		}
	}
}


static inline void _RPolyGroupsPrint(E3dRPolyGroup* const* PRPolyGroups, const EIndex PRPolyGroups_Count)
{
	EIndex  LPGI = 0;for(;LPGI < PRPolyGroups_Count;LPGI++)
	{
		const E3dRPolyGroup*  LRPolyGroup = PRPolyGroups[LPGI];
		const EIndex          LPN = LRPolyGroup->TrianglesNew.Count;
		const EIndex          LRTriangleSize = LRPolyGroup->RTriangleSize;

printf("%s() %s:%d  PG %lld  Triangles: %lld\n", __FUNCTION__, __FILE__, __LINE__, (long long)LPGI, (long long)LPN);fflush(stdout);
		EIndex  LPI = 0;const E3dRTriangle*  LRTriangle = LRPolyGroup->TrianglesNew.Items;for(;LPI < LPN;LPI++, EStruct_Inc(LRTriangle, LRTriangleSize))
		{
			printf("  %5lld:  V0 % 9.6f % 9.6f % 9.6f  E1 % 9.6f % 9.6f % 9.6f  E2 % 9.6f % 9.6f % 9.6f\n", (long long)LPI, XYZ_s(LRTriangle->Vertices[0].Position), XYZ_s(LRTriangle->Edge1), XYZ_s(LRTriangle->Edge2));fflush(stdout);
		}
	}
}


typedef struct
{
  EThreadSet1DCore;

	E3dInfo*               ObjectComponent;
	const E3dMesh*         Mesh0;E3dBEdge*  Mesh0BEdges;E3dBVertex*  Mesh0BVertices;E3dBPolygon**  Mesh0BPolygons;
	const E3dAccelerator*  Mesh1Accelerator;
	E3dBPolygon**          Mesh1BPolygons;
	uint8_t                Mode;
} EThreadSet1DEdgeTriangleInt;

//================================================================
// Multi-threaded Edge-Polygon intersector main function
//================================================================
static void* _EdgePolygonsIntersect_Thread(void* PThread)
{
  EThread*                      LThread = (EThread*)PThread;
  EThreadSet1DEdgeTriangleInt*  LThreadSet = (EThreadSet1DEdgeTriangleInt*)LThread->ThreadSet;
  const EIndex                  LEN = LThreadSet->NWorkUnits;
  E3dInfo*                      LObjectComponent = LThreadSet->ObjectComponent;
  const E3dMesh*                LMesh0 = LThreadSet->Mesh0;
  E3dBEdge*                     LMesh0BEdges = LThreadSet->Mesh0BEdges;
  E3dBVertex*                   LMesh0BVertices = LThreadSet->Mesh0BVertices;
  E3dBPolygon**                 LMesh0BPolygons = LThreadSet->Mesh0BPolygons;
  E3dBPolygon**                 LMesh1BPolygons = LThreadSet->Mesh1BPolygons;
	EIndex                        LTotalIntersections = 0;
	const E3dAccelerator*         LMesh1Accelerator = LThreadSet->Mesh1Accelerator;

	E3dRWAccelerator              LMesh1RWAccelerator;E3dAccelerator_CopyToThread(LMesh1Accelerator, &LMesh1RWAccelerator, TRUE, &LThreadSet->GetWorkload_mutex);
	E3dAccelerator_ResetForRendering(LMesh1Accelerator, &LMesh1RWAccelerator);

	const uint8_t                 LMode = LThreadSet->Mode;
	const EIndex                  LWorkUnits = 64;

//E3dBVH*  LBVH = LMesh1RWAccelerator.BVH;
//wprintf("%s() %s:%d  Thread %s %2lld%s START  %sBVH%s  PTree %s%p%s RefCnt % 3lld  NumOfAllocatedNodes %lld  NextFreeNodeIndex %lld\n", __FUNCTION__, __FILE__, __LINE__,  ECodeCyan, (long long)LThread->Number, ECodeNormal,  ECodeGreen, ECodeNormal,  ECodeCyan, LBVH, ECodeNormal,  (long long)LBVH->RefCnt, (long long)LBVH->NumOfAllocatedNodes, (long long)LBVH->NextFreeNodeIndex);fflush(stdout);

  while(1)
  {
    E_THREAD_LOCK(LThreadSet->GetWorkload);
    EIndex  LEI = LThreadSet->NextWorkUnit;if(LEI >= LEN)
    {
      E_THREAD_UNLOCK(LThreadSet->GetWorkload);
      break;
    }
		EIndex  LBlockEnd = LEI + LWorkUnits;if(LBlockEnd > LEN) LBlockEnd = LEN;LThreadSet->NextWorkUnit = LBlockEnd;
    E_THREAD_UNLOCK(LThreadSet->GetWorkload);

		const E3dEdge*  LEdge = LMesh0->Edges.Items + LEI;const E3dWingedEdge*  LWingedEdge = LMesh0->WingedEdges + LEI;E3dBEdge*  LBEdge = LMesh0BEdges + LEI;for(;LEI < LBlockEnd;LEI++, LEdge++, LWingedEdge++, LBEdge++)
		{
			const EIndex  LIntersections = _EdgePolysIntersect(LObjectComponent, LMode, LEI, LEdge, LBEdge, LMesh0BVertices, LMesh1BPolygons,  &LMesh1RWAccelerator);if(LIntersections)
			{
				LTotalIntersections += LIntersections;

// Mark Polygons that share this Edge as EdgeTrimmed
//
				EIndex  LPI = 0;const EIndex  LPN = LWingedEdge->Polygons_Count;const E3dEdgePolygonInfo*  LEdgePolygonInfo = LWingedEdge->Polygons;for(;LPI < LPN;LPI++, LEdgePolygonInfo++)
				{
					const E3dPolyGroup*  LPolyGroup = LMesh0->PolyGroups.Items[LEdgePolygonInfo->PolyGroupIndex];
					const EIndex         LPolyGroupIndex = LPolyGroup->TmpIndex0;
					const E3dPolygon*    LPolygon = LEdgePolygonInfo->Polygon;
					E3dBPolygon*         LBPolygon = LMesh0BPolygons[LPolyGroupIndex] + (LPolygon - LPolyGroup->Polygons.Items);

					if(!LBPolygon->EdgeTrimmed) LBPolygon->EdgeTrimmed = TRUE;
				}
			}
		}
  }

//printf("%s() %s:%d  Thread %s %2lld%s FREE   %sBVH%s  PTree %s%p%s RefCnt % 3lld  NumOfAllocatedNodes %lld  NextFreeNodeIndex %lld\n", __FUNCTION__, __FILE__, __LINE__,  ECodeCyan, (long long)LThread->Number, ECodeNormal,  ECodeGreen, ECodeNormal,  ECodeCyan, LBVH, ECodeNormal,  (long long)LBVH->RefCnt, (long long)LBVH->NumOfAllocatedNodes, (long long)LBVH->NextFreeNodeIndex);fflush(stdout);
	E3dRWAccelerator_FreeContents(&LMesh1RWAccelerator, &LThreadSet->GetWorkload_mutex, TRUE);
//printf("%s() %s:%d  Thread %s %2lld%s EXIT   %sBVH%s  PTree %s%p%s RefCnt % 3lld  NumOfAllocatedNodes %lld  NextFreeNodeIndex %lld\n", __FUNCTION__, __FILE__, __LINE__,  ECodeCyan, (long long)LThread->Number, ECodeNormal,  ECodeGreen, ECodeNormal,  ECodeCyan, LBVH, ECodeNormal,  (long long)LBVH->RefCnt, (long long)LBVH->NumOfAllocatedNodes, (long long)LBVH->NextFreeNodeIndex);fflush(stdout);
//EList_FreeChk(LMesh1RWAccelerator.TriangleMailboxes);EList_FreeAChk_s(LMesh1RWAccelerator.CustomShapes);

  return(NULL);
}  


//========================================================================================================
// Main algorithm
// It uses PMesh0's coordinate system as a base, to minimize precision loss by minimizing transforms.
// So, PDstMesh must be in the same coordinate system as PMesh0 (it may be the same).
//========================================================================================================
static int _Boolean(E3dInfo* PInfo, const E3dMesh* PMesh0, const E3dAccelerator* PMesh0Accelerator,  const E3dMesh* PMesh1, const E3dAccelerator* PMesh1Accelerator,  const E3dMatrix PMesh1_to_Mesh0Matrix,  const E3dMatrix PMesh1_to_Mesh0NormalMatrix,  E3dMesh* PDstMesh, const EBool PMesh0Closed)
{
	int  LResult = E_SUCCESS;

tm_PUSH("_Boolean");

	E3dScene*   LScene = E3d_Scene;
	const int   LOperation = PInfo->Operation;

	EIndex      LTotalIntersections = 0;

#if PlTIMING == 2
	ETime       LPrev = E_GetMicroseconds();
#endif


tm_PUSH("_Boolean_Prep0");

	const E3dVertexSize  LVertexCoreSize = E3dVertexCore_Size(), LDstMeshVertexSize = PMesh0->VertexSize;const E3dVertexSize  LDstMeshVertexSizeAboveCore = LDstMeshVertexSize - LVertexCoreSize;

// For Mesh0, just copy Vertex positions into LMesh0BVertices
//
	E3dBVertex*  LMesh0BVertices = NULL;const EIndex  LVN0 = PMesh0->Vertices.Count;
	{
		ENewN(LVN0, LMesh0BVertices);

		EIndex  LI = 0;const E3dVertex*  LVertex = PMesh0->Vertices.Items;const E3dVertexSize  LMesh0VertexSize = LDstMeshVertexSize;E3dBVertex*  LBVertex = LMesh0BVertices;for(;LI < LVN0;LI++, EStruct_Inc(LVertex, LMesh0VertexSize), LBVertex++)
		{
			memcpy(LBVertex, LVertex, LVertexCoreSize);  // E3dBVertex only has the Vertex core (Position, Flags)
			LBVertex->State = E3dOUTSIDE;
//printf("%s() %s:%d  %d  % 7.2f,% 7.2f,% 7.2f\n", __FUNCTION__, __FILE__, __LINE__, LI, XYZ_s(LBVertex->Position));fflush(stdout);
		}
	}


	E3dBPolygon**  LMesh0BPolygons = NULL;const EIndex  LMesh0BPolygons_Count = PMesh0->PolyGroups.Count;ENewNClr(LMesh0BPolygons_Count, LMesh0BPolygons);  // PMesh0's PolyGroups are mirrored in these, to carry temporary data

	{
		EIndex  LPGI = 0;const EIndex  LPGN = PMesh0->PolyGroups.Count;for(;LPGI < LPGN;LPGI++)
		{
			E3dPolyGroup*  LPolyGroup = PMesh0->PolyGroups.Items[LPGI];
			ENewNClr(LPolyGroup->Polygons.Count, LMesh0BPolygons[LPGI]);
			EIndex  LPI = 0;const EIndex  LPN = LPolyGroup->Polygons.Count;const E3dPolygon*  LPolygon = LPolyGroup->Polygons.Items;E3dBPolygon*  LBPolygon = LMesh0BPolygons[LPGI];for(;LPI < LPN;LPI++, LPolygon++, LBPolygon++)
			{
// For Mesh0, just copy the Polygon normals
//
				LBPolygon->BNormal = LPolygon->Normal;
			}
		}
	}



// For, Mesh1, transform the Vertices into Mesh0's coordinate system
//
	E3dBVertex*  LMesh1BVertices = NULL;
	{
		const EIndex  LN = PMesh1->Vertices.Count;ENewN(LN, LMesh1BVertices);

		const E3dVertex*  LVertex = PMesh1->Vertices.Items;const E3dVertexSize  LMesh1VertexSize = PMesh1->VertexSize;
		EIndex  LI = 0;E3dBVertex*  LBVertex = LMesh1BVertices;for(;LI < LN;LI++, EStruct_Inc(LVertex, LMesh1VertexSize), LBVertex++)
		{
			memcpy(LBVertex, LVertex, LVertexCoreSize);  // E3dBVertex only has the Vertex core (Position, Flags)
			EMatrix_Transform3x4_vv(PMesh1_to_Mesh0Matrix, LVertex->Position, LBVertex->Position);
			LBVertex->State = E3dOUTSIDE;
		}
	}

	E3dBPolygon**  LMesh1BPolygons = NULL;const EIndex  LMesh1BPolygons_Count = PMesh1->PolyGroups.Count;ENewNClr(LMesh1BPolygons_Count, LMesh1BPolygons);	// PMesh0's PolyGroups are mirrored in these, to carry temporary data

	{
		EIndex  LPGI = 0;const EIndex  LPGN = PMesh1->PolyGroups.Count;for(;LPGI < LPGN;LPGI++)
		{
			E3dPolyGroup*  LPolyGroup = PMesh1->PolyGroups.Items[LPGI];
			ENewNClr(LPolyGroup->Polygons.Count, LMesh1BPolygons[LPGI]);
			EIndex  LPI = 0;const EIndex  LPN = LPolyGroup->Polygons.Count;const E3dPolygon*  LPolygon = LPolyGroup->Polygons.Items;E3dBPolygon*  LBPolygon = LMesh1BPolygons[LPGI];for(;LPI < LPN;LPI++, LPolygon++, LBPolygon++)
			{
// For Mesh1, transform Polygon normals with PMesh1_to_Mesh0NormalMatrix and normalize them (same as in E3dObject_GetRenderShapes()!)
//
				EMatrix_Transform3x3_vv(PMesh1_to_Mesh0NormalMatrix, LPolygon->Normal, LBPolygon->BNormal);E3d_Normalize3_v(LBPolygon->BNormal);
			}
		}
	}


#if PlTIMING == 2
{ const ETime  LNow = E_GetMicroseconds();char  s[256];printf("%s() %s:%d  transforms : %s\n", __FUNCTION__, __FILE__, __LINE__, EStr_MicrosecondsToString((LNow - LPrev), s, FALSE));fflush(stdout);LPrev = LNow; }
#endif

// Pre-compute Edge directions and lengths
//
	E3dBEdge*  LMesh0BEdges;

	{
		const EIndex  LEN = PMesh0->Edges.Count;
		ENewNClr(LEN, LMesh0BEdges);  // PMesh0's Edges are mirrored in these, to carry temporary data

		{
			EIndex  LEC = 0;const E3dEdge*  LEdge = PMesh0->Edges.Items;E3dBEdge*  LBEdge = LMesh0BEdges;for(;LEC < LEN;LEC++, LEdge++, LBEdge++)
			{
				const E3dBVertex*  LSVertex = LMesh0BVertices + LEdge->Start;
				const E3dBVertex*  LEVertex = LMesh0BVertices + LEdge->End;

#if E3dPRESERVE_EDGES
				LBEdge->Start = LEdge->Start;
				LBEdge->End   = LEdge->End;
#endif

				LBEdge->Origin   = LSVertex->Position;
				LBEdge->EndPoint = LEVertex->Position;
				E3dCoord3  E_Sub3i_vv(LDiff, LEVertex->Position, LSVertex->Position);
				E3d_NormalizeGetLength3_vv(LDiff, LBEdge->Direction, LBEdge->Length);
			}
		}
	}



// Pre-compute Edge directions and lengths
//
	E3dBEdge*  LMesh1BEdges;const EIndex  LMesh1BEdges_Count = PMesh1->Edges.Count;

	{
		const EIndex  LEN = PMesh1->Edges.Count;
		ENewNClr(LEN, LMesh1BEdges);  // PMesh1's Edges are mirrored in these, to carry temporary data

		{
			EIndex  LEC = 0;const E3dEdge*  LEdge = PMesh1->Edges.Items;E3dBEdge*  LBEdge = LMesh1BEdges;for(;LEC < LEN;LEC++, LEdge++, LBEdge++)
			{
				const E3dBVertex*  LSVertex = LMesh1BVertices + LEdge->Start;
				const E3dBVertex*  LEVertex = LMesh1BVertices + LEdge->End;

#if E3dPRESERVE_EDGES
				LBEdge->Start = LEdge->Start;
				LBEdge->End   = LEdge->End;
#endif

				LBEdge->Origin   = LSVertex->Position;
				LBEdge->EndPoint = LEVertex->Position;
				E3dCoord3  E_Sub3i_vv(LDiff, LEVertex->Position, LSVertex->Position);
				E3d_NormalizeGetLength3_vv(LDiff, LBEdge->Direction, LBEdge->Length);
			}
		}
	}
tm_POP();


	EIndex*  LMesh1ToDstMeshVertexIndexTable;ENewN(PMesh1->Vertices.Count, LMesh1ToDstMeshVertexIndexTable);ESetN(LMesh1ToDstMeshVertexIndexTable, -1, PMesh1->Vertices.Count);

	if(LMesh0BVertices && LMesh1BVertices)
	{
		E3dRWAccelerator  LMesh0RWAccelerator;E3dAccelerator_CopyToThread(PMesh0Accelerator, &LMesh0RWAccelerator, TRUE, NULL);E3dAccelerator_ResetForRendering(PMesh0Accelerator, &LMesh0RWAccelerator);
		E3dRWAccelerator  LMesh1RWAccelerator;E3dAccelerator_CopyToThread(PMesh1Accelerator, &LMesh1RWAccelerator, TRUE, NULL);E3dAccelerator_ResetForRendering(PMesh1Accelerator, &LMesh1RWAccelerator);

//printf("%s() %s:%d  PMesh0->Vertices.Count %lld  PDstMesh->Vertices.Count %lld\n", __FUNCTION__, __FILE__, __LINE__, (long long)PMesh0->Vertices.Count, (long long)PDstMesh->Vertices.Count);fflush(stdout);
		if(_AcceleratorValid(&LMesh0RWAccelerator))
		{
tm_PUSH("_Boolean_Prep1");
// Mark Vertices of Mesh1 whether they are inside or outside of Mesh0
//
			switch(LOperation)
			{
				case E3dSUBTRACTION:
				case E3dINTERSECTION:
					if(!PInfo->CheckIfMesh0IsClosed || PMesh0Closed)
					{
//printf("%s() %s:%d", __FUNCTION__, __FILE__, __LINE__);fflush(stdout);
						EIndex  LI = 0;const EIndex  LN = PMesh1->Vertices.Count;E3dBVertex*  LBVertex = LMesh1BVertices;for(;LI < LN;LI++, LBVertex++)
						{
#if E3dDEBUG
							if(_Verbose) { printf("%s() %s:%d  Mesh1 Vtx %5lld", __FUNCTION__, __FILE__, __LINE__, (long long)LI);fflush(stdout); }
#endif
							if(E3d_PointInMesh(PMesh0, &LMesh0RWAccelerator, &LBVertex->Position))
							{
								LBVertex->State = E3dINSIDE;

// If Mesh0 is closed, add the Mesh1 Vertices that are inside Mesh0 to PDstMesh
								{
									E3dVertex*  LDVertex = E3dMesh_VerticesAdd(PDstMesh, 1);if(LDVertex)
									{
										memcpy(LDVertex, LBVertex, LVertexCoreSize);  // E3dBVertex only has the Vertex core (Position, Flags)

// Set the rest of LDVertex to 0, if LDstMeshVertexSizeAboveCore > 0
//
										if(LDstMeshVertexSizeAboveCore > 0) memset((uint8_t*)LDVertex + LVertexCoreSize, 0, LDstMeshVertexSizeAboveCore);

//										LDVertex->Flags |= E3dVtxACTIVE;
//printf(" %d -> %d", LI, PDstMesh->Vertices.Count - 1);fflush(stdout);
										LMesh1ToDstMeshVertexIndexTable[LI] = PDstMesh->Vertices.Count - 1;
									}
								}

							}
#if E3dDEBUG
							if(_Verbose) { printf("\n");fflush(stdout); }
#endif
						}
//printf("\n");fflush(stdout);
					}
				break;

				case E3dUNION:
					{
						EIndex  LI = 0;const EIndex  LN = PMesh1->Vertices.Count;E3dBVertex*  LBVertex = LMesh1BVertices;for(;LI < LN;LI++, LBVertex++)
						{
#if E3dDEBUG
							if(_Verbose) { printf("%s() %s:%d  Mesh1 Vtx %lld", __FUNCTION__, __FILE__, __LINE__, (long long)LI);fflush(stdout); }
#endif
							if(E3d_PointInMesh(PMesh0, &LMesh0RWAccelerator, &LBVertex->Position))
							{
								LBVertex->State = E3dINSIDE;

#if E3dDEBUG
								if(_Verbose) { printf(" %sIN%s", ECodeGreen, ECodeNormal);fflush(stdout); }
#endif
							}

							{
// Add Mesh1 Vertices to PDstMesh
//
								E3dVertex*  LDVertex = E3dMesh_VerticesAdd(PDstMesh, 1);if(LDVertex)
								{
									memcpy(LDVertex, LBVertex, LVertexCoreSize);  // E3dBVertex only has the Vertex core (Position, Flags)

// Set the rest of LDVertex to 0, if LDstMeshVertexSizeAboveCore > 0
//
									if(LDstMeshVertexSizeAboveCore > 0) memset((uint8_t*)LDVertex + LVertexCoreSize, 0, LDstMeshVertexSizeAboveCore);

//									LDVertex->Flags |= E3dVtxACTIVE;
//printf(" %d -> %d", LI, PDstMesh->Vertices.Count - 1);fflush(stdout);
									LMesh1ToDstMeshVertexIndexTable[LI] = PDstMesh->Vertices.Count - 1;
								}

							}
#if E3dDEBUG
							if(_Verbose) { printf("\n");fflush(stdout); }
#endif
						}
					}
				break;
			}
tm_POP();
		}
		else
		{
printf("%s() %s:%d  Mesh0 <%s> has no accelerator\n", __FUNCTION__, __FILE__, __LINE__, PMesh0->Name);fflush(stdout);
		}


#if PlTIMING == 2
{ ETime	LNow = E_GetMicroseconds();char	s[256];printf("%s() %s:%d  M1 verts inside M0 : %s\n", __FUNCTION__, __FILE__, __LINE__, EStr_MicrosecondsToString((LNow - LPrev), s, FALSE));fflush(stdout);LPrev = LNow; }
#endif

// Mark Vertices of Mesh0 whether they are inside or outside of Mesh1
//
#if E3dDEBUG
_Verbose = TRUE;
#endif
		if(_AcceleratorValid(&LMesh1RWAccelerator))
		{
			EIndex  LI = 0;E3dBVertex*  LBVertex = LMesh0BVertices;for(;LI < LVN0;LI++, LBVertex++)
			{
#if E3dDEBUG
if(_Verbose) { printf("%s() %s:%d  Mesh0 Vtx %5lld", __FUNCTION__, __FILE__, __LINE__, (long long)LI);fflush(stdout); }
#endif
				if(E3d_PointInMesh(PMesh1, &LMesh1RWAccelerator, &LBVertex->Position))
				{
					LBVertex->State = E3dINSIDE;

					{
//						E3dVertex*  LDVertex = EStruct_ByIndex(PMesh0->Vertices, PMesh0->VertexSize, LI);
//						LDVertex->Flags |= E3dVtxACTIVE;
					}

				}
#if E3dDEBUG
if(_Verbose) { printf("\n");fflush(stdout); }
#endif
			}
		}
		else
		{
printf("%s() %s:%d  %sMesh1 <%s> has no accelerator%s\n", __FUNCTION__, __FILE__, __LINE__, ECodeRed, PMesh1->Name, ECodeNormal);fflush(stdout);
		}
#if E3dDEBUG
_Verbose = FALSE;
#endif


#if PlTIMING == 2
{ const ETime  LNow = E_GetMicroseconds();char  s[256];printf("%s() %s:%d  M0 verts inside M1 : %s\n", __FUNCTION__, __FILE__, __LINE__, EStr_MicrosecondsToString((LNow - LPrev), s, FALSE));fflush(stdout);LPrev = LNow; }
#endif


// Set TmpIndex0 in the PolyGroups of Mesh0 to the index of the PolyGroup within the Mesh, for fast tracking with LMesh0BPolygons
//
		{
			EIndex  LPGI = 0;const EIndex  LPGN = PMesh0->PolyGroups.Count;for(;LPGI < LPGN;LPGI++)
			{
				E3dPolyGroup*  LPolyGroup = PMesh0->PolyGroups.Items[LPGI];
				LPolyGroup->TmpIndex0 = LPGI;
			}
		}

// Set TmpIndex0 in the PolyGroups of Mesh1 to the index of the PolyGroup within the Mesh, for fast tracking with LMesh1BPolygons
//
		{
			EIndex  LPGI = 0;const EIndex  LPGN = PMesh1->PolyGroups.Count;for(;LPGI < LPGN;LPGI++)
			{
				E3dPolyGroup*  LPolyGroup = PMesh1->PolyGroups.Items[LPGI];
				LPolyGroup->TmpIndex0 = LPGI;
			}
		}


#if PlTIMING == 2
LPrev = E_GetMicroseconds();
#endif

// Intersect Edges of Mesh0 with Edges of Mesh1
//
// E3dFIXME brute force Edge-Edge intersection for now. This is O(n2)
// Idea :
// - Put Edges in BVHs
// - Trace Each Edge of Mesh0 as ray in Mesh1's BVH
// - Only do intersection tests with Edges in BVH cells
//
#if EDGE_EDGE_INTERSECT
		_EdgeEdgeIntersect(PMesh0, LMesh0BEdges, LMesh0BPolygons,  PMesh1, LMesh1BEdges, LMesh1BPolygons,  LInfo, PDstMesh);
#endif

#if PlTIMING == 2
{ const ETime  LNow = E_GetMicroseconds();char  s[256];printf("%s() %s:%d  Edge-Edge intersections : %s\n", __FUNCTION__, __FILE__, __LINE__, EStr_MicrosecondsToString((LNow - LPrev), s, FALSE));fflush(stdout);LPrev = LNow; }
#endif 


		EIndex                       LNThreads = 1;
		EThreadSet1DEdgeTriangleInt  LThreadSet;

		if(_EdgePolygonIntersectMultiThreaded && (PMesh0->Edges.Count > 1024 || PMesh1->Edges.Count > 1024))
		{
			LNThreads = EThreads_OptimalThreadCount(PMesh0->Edges.Count);

//printf("%s() %s:%d  %sThreads%s %lld\n", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, ECodeNormal, (long long)LNThreads);fflush(stdout);

			if(LNThreads > 1) EThreadSet1D_Init((EThreadSet1D*)&LThreadSet);
			LThreadSet.ObjectComponent = PInfo;
		}

// Intersect the Edges of PMesh0 with the Polygons of PMesh1
//
		if(_AcceleratorValid(&LMesh1RWAccelerator))
		{
tm_PUSH("PMesh0Edges-X-PMesh1Polys");
			if(_EdgePolygonIntersectMultiThreaded && PMesh0->Edges.Count > 1024)
			{
				LThreadSet.Mesh0 = PMesh0;
				LThreadSet.Mesh0BEdges = LMesh0BEdges;
				LThreadSet.Mesh0BVertices = LMesh0BVertices;
				LThreadSet.Mesh0BPolygons = LMesh0BPolygons;
				LThreadSet.Mesh1Accelerator = PMesh1Accelerator;
				LThreadSet.Mesh1BPolygons = LMesh1BPolygons;
				LThreadSet.Mode = E3dON_MESHA_EDGE_MESHB_SURFACE;
printf("%s() %s:%d  %sThreads%s %lld\n", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, ECodeNormal, (long long)LNThreads);fflush(stdout);
				LThreadSet.MaxNThreads = LNThreads;
				EThreadSet1D_Run((EThreadSet1D*)&LThreadSet, PMesh0->Edges.Count,  sizeof(EThread), NULL, NULL,  _EdgePolygonsIntersect_Thread);
				EThreadSet1D_FreeContents((EThreadSet1D*)&LThreadSet);
			}
			else
			{
				EIndex  LEC = 0;const EIndex  LEN = PMesh0->Edges.Count;const E3dEdge*  LEdge = PMesh0->Edges.Items;const E3dWingedEdge*  LWingedEdge = PMesh0->WingedEdges;E3dBEdge*  LBEdge = LMesh0BEdges;for(;LEC < LEN;LEC++, LEdge++, LWingedEdge++, LBEdge++)
				{
					const EIndex  LIntersections = _EdgePolysIntersect(PInfo, E3dON_MESHA_EDGE_MESHB_SURFACE, LEC, LEdge, LBEdge, LMesh0BVertices, LMesh1BPolygons,  &LMesh1RWAccelerator);if(LIntersections)
					{
						LTotalIntersections += LIntersections;
// Mark Polygons that share this Edge as EdgeTrimmed
//
						EIndex  LPI = 0;const EIndex  LPN = LWingedEdge->Polygons_Count;const E3dEdgePolygonInfo*  LEdgePolygonInfo = LWingedEdge->Polygons;for(;LPI < LPN;LPI++, LEdgePolygonInfo++)
						{
							const E3dPolyGroup*  LPolyGroup = PMesh0->PolyGroups.Items[LEdgePolygonInfo->PolyGroupIndex];
							const EIndex         LPolyGroupIndex = LPolyGroup->TmpIndex0;
							const E3dPolygon*    LPolygon = LEdgePolygonInfo->Polygon;
							E3dBPolygon*         LBPolygon = LMesh0BPolygons[LPolyGroupIndex] + (LPolygon - LPolyGroup->Polygons.Items);

							if(!LBPolygon->EdgeTrimmed) LBPolygon->EdgeTrimmed = TRUE;
						}
					}
				}
			}
tm_POP();
		}
		else
		{
printf("%s() %s:%d  %sMesh1 <%s> has no accelerator%s\n", __FUNCTION__, __FILE__, __LINE__, ECodeRed, PMesh1->Name, ECodeNormal);fflush(stdout);
		}


// Intersect the Edges of PMesh1 with the Polygons of PMesh0
//
		if(_AcceleratorValid(&LMesh0RWAccelerator))
		{
tm_PUSH("PMesh1Edges-X-PMesh0Polys");
			if(_EdgePolygonIntersectMultiThreaded && PMesh1->Edges.Count > 1024)
			{
				LThreadSet.Mesh0 = PMesh1;
				LThreadSet.Mesh0BEdges = LMesh1BEdges;
				LThreadSet.Mesh0BVertices = LMesh1BVertices;
				LThreadSet.Mesh0BPolygons = LMesh1BPolygons;
				LThreadSet.Mesh1Accelerator = PMesh0Accelerator;
				LThreadSet.Mesh1BPolygons = LMesh0BPolygons;
				LThreadSet.Mode = E3dON_MESHB_EDGE_MESHA_SURFACE;
printf("%s() %s:%d  %sThreads%s %lld\n", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, ECodeNormal, (long long)LNThreads);fflush(stdout);
				LThreadSet.MaxNThreads = LNThreads;
				EThreadSet1D_Run((EThreadSet1D*)&LThreadSet, PMesh1->Edges.Count,  sizeof(EThread), NULL, NULL,  _EdgePolygonsIntersect_Thread);
				EThreadSet1D_FreeContents((EThreadSet1D*)&LThreadSet);
			}
			else
			{
				EIndex  LEC = 0;const EIndex  LEN = PMesh1->Edges.Count;const E3dEdge*  LEdge = PMesh1->Edges.Items;const E3dWingedEdge*  LWingedEdge = PMesh1->WingedEdges;E3dBEdge*  LBEdge = LMesh1BEdges;for(;LEC < LEN;LEC++, LEdge++, LWingedEdge++, LBEdge++)
				{
					const EIndex  LIntersections = _EdgePolysIntersect(PInfo, E3dON_MESHB_EDGE_MESHA_SURFACE, LEC, LEdge, LBEdge, LMesh1BVertices, LMesh0BPolygons,  &LMesh0RWAccelerator);if(LIntersections)
					{
						LTotalIntersections += LIntersections;

// Mark Polygons that share this Edge as EdgeTrimmed
//
						EIndex  LPI = 0;const EIndex  LPN = LWingedEdge->Polygons_Count;const E3dEdgePolygonInfo*  LEdgePolygonInfo = LWingedEdge->Polygons;for(;LPI < LPN;LPI++, LEdgePolygonInfo++)
						{
							const E3dPolyGroup*  LPolyGroup = PMesh1->PolyGroups.Items[LEdgePolygonInfo->PolyGroupIndex];
							const EIndex         LPolyGroupIndex = LPolyGroup->TmpIndex0;
							const E3dPolygon*    LPolygon = LEdgePolygonInfo->Polygon;
							E3dBPolygon*         LBPolygon = LMesh1BPolygons[LPolyGroupIndex] + (LPolygon - LPolyGroup->Polygons.Items);

							if(!LBPolygon->EdgeTrimmed) LBPolygon->EdgeTrimmed = TRUE;
						}
					}
				}
			}
tm_POP();
		}
		else
		{
printf("%s() %s:%d  Mesh0 <%s> has no accelerator\n", __FUNCTION__, __FILE__, __LINE__, PMesh0->Name);fflush(stdout);
		}


		if(LNThreads > 1)
		{
			EThreadSet1D_FreeContents((EThreadSet1D*)&LThreadSet);
		}


#if PlTIMING == 2
{ const ETime  LNow = E_GetMicroseconds();char  s[256];printf("%s() %s:%d  Edge-Polygon intersections : %s\n", __FUNCTION__, __FILE__, __LINE__, EStr_MicrosecondsToString((LNow - LPrev), s, FALSE));fflush(stdout);LPrev = LNow; }
#endif


// BNewVertices "gather phase": Build PInfo->BNewVertices list from LMesh0BEdges NewVertices and LMesh1BEdges NewVertices
//
		{
tm_PUSH("Collect_PInfo->BNewVertices Mesh0 Edges");
//printf("%s() %s:%d  %sE0-P1%s\n", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, ECodeNormal);fflush(stdout);
			EIndex  LEI = 0;const EIndex  LEN = PMesh0->Edges.Count;const E3dBEdge*  LBEdge = LMesh0BEdges;for(;LEI < LEN;LEI++, LBEdge++)
			{
//printf("%s() %s:%d    %sEdge % 4lld%s\n", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, (long long)LEI, ECodeNormal);fflush(stdout);
				EIndex  LVI = 0;const EIndex  LVN = LBEdge->NewVertices.Count;E3dBEdgeVertex*  LBEdgeVertex = LBEdge->NewVertices.Items;for(;LVI < LVN;LVI++, LBEdgeVertex++)
				{
					LBEdgeVertex->IndexInMesh = PInfo->BNewVertices_Count;
					E3dBNewVertex*  LBNewVertex = EList_StructAddA(PInfo->BNewVertices, 256);if(LBNewVertex)
					{
						LBNewVertex->Position = LBEdgeVertex->Position;
						LBNewVertex->EdgeT = LBEdgeVertex->EdgeT;
						LBNewVertex->IndexInMesh = PInfo->BNewVertices_Count - 1;
						LBNewVertex->Mode = LBEdgeVertex->Mode;
						LBNewVertex->EdgeIndex = LEI;

						LBNewVertex->RTriangle = LBEdgeVertex->RTriangle;LBNewVertex->Face = LBEdgeVertex->Face;
						LBNewVertex->UV = LBEdgeVertex->UV;

//printf("%s() %s:%d      % 7.3f,% 7.3f,% 7.3f  % 7.3f  %4lld %02x\n", __FUNCTION__, __FILE__, __LINE__, XYZ_s(LBNewVertex->Position), LBNewVertex->EdgeT, (long long)LBNewVertex->IndexInMesh, LBNewVertex->Mode);fflush(stdout);
					}
				}
			}
tm_POP();
		}

		{
tm_PUSH("Collect_PInfo->BNewVertices Mesh1 Edges");
			EIndex  LEI = 0;const EIndex  LEN = PMesh1->Edges.Count;const E3dBEdge*  LBEdge = LMesh1BEdges;for(;LEI < LEN;LEI++, LBEdge++)
			{
				EIndex  LVI = 0;const EIndex  LVN = LBEdge->NewVertices.Count;E3dBEdgeVertex*  LBEdgeVertex = LBEdge->NewVertices.Items;for(;LVI < LVN;LVI++, LBEdgeVertex++)
				{
					LBEdgeVertex->IndexInMesh = PInfo->BNewVertices_Count;
					E3dBNewVertex*  LBNewVertex = EList_StructAddA(PInfo->BNewVertices, 256);if(LBNewVertex)
					{
						LBNewVertex->Position = LBEdgeVertex->Position;
						LBNewVertex->EdgeT = LBEdgeVertex->EdgeT;
						LBNewVertex->IndexInMesh = PInfo->BNewVertices_Count - 1;
						LBNewVertex->Mode = LBEdgeVertex->Mode;
						LBNewVertex->EdgeIndex = LEI;

						LBNewVertex->RTriangle = LBEdgeVertex->RTriangle;LBNewVertex->Face = LBEdgeVertex->Face;
						LBNewVertex->UV = LBEdgeVertex->UV;
					}
				}
			}
		}
tm_POP();

		E3dRWAccelerator_FreeContents(&LMesh0RWAccelerator, NULL, TRUE);
		E3dRWAccelerator_FreeContents(&LMesh1RWAccelerator, NULL, TRUE);
	}


//tm_PUSH("_EdgesCheck");
	_EdgesCheck(PMesh0, LMesh0BEdges,  LMesh0BVertices);
	_EdgesCheck(PMesh1, LMesh1BEdges,  LMesh1BVertices);
//tm_POP();


	const EIndex  LMesh0VerticesCountBeforeAddingIntersectedVertices = PDstMesh->Vertices.Count;
	const EIndex  LMesh1VerticesCountBeforeAddingIntersectedVertices = PMesh1->Vertices.Count;


// Add new Vertices to PDstMesh
//

//printf("%s() %s:%d  New Vertices %lld\n", __FUNCTION__, __FILE__, __LINE__, (long long)LInfo->BNewVertices_Count);fflush(stdout);

	E3dVertex*  LMesh1AllVertices = NULL;    // All Vertices of Mesh1, including the original ones and new ones.

	if(PInfo->BNewVertices_Count > 0)
	{
//tm_PUSH("New Vtx");
		const EIndex  LVN = PInfo->BNewVertices_Count;

		{
			E3dVertex*  LVertex = E3dMesh_VerticesAdd(PDstMesh, LVN);if(LVertex)
			{
				const E3dVertexSize  LVertexSize = PDstMesh->VertexSize;const E3dVertexSize  LVertexSizeAboveCore = LVertexSize - LVertexCoreSize;
				EIndex  LVI = 0;const E3dBNewVertex*  LBNewVertex = PInfo->BNewVertices;for(;LVI < LVN;LVI++, LBNewVertex++, EStruct_Inc(LVertex, LVertexSize))
				{
					LVertex->Position = LBNewVertex->Position;
					if(LVertexSizeAboveCore > 0) memset((uint8_t*)LVertex + LVertexCoreSize, 0, LVertexSizeAboveCore);

					switch(LBNewVertex->Mode)
					{
						case E3dON_MESHA_EDGE_MESHB_SURFACE:
							if(_SelectVertexMesh0Edge_Mesh1Surface) LVertex->Flags |= E3dVtxSELECTED;
						break;

						case E3dON_MESHB_EDGE_MESHA_SURFACE:
							if(_SelectVertexMesh1Edge_Mesh0Surface) LVertex->Flags |= E3dVtxSELECTED;
						break;
					}
				}
			}
		}


// Add new Vertices to PMesh1
//
		{
#if E3dDebugKEEP_VERTS_ON_MESHB
			E3dMatrix  LMesh0_to_Mesh1Matrix;E3d_Matrix4x4Invert(PMesh1_to_Mesh0Matrix, LMesh0_to_Mesh1Matrix);
#endif

			EResizeNT(EIndex, LMesh1VerticesCountBeforeAddingIntersectedVertices + LVN, LMesh1ToDstMeshVertexIndexTable);

			const E3dVertexSize  LMesh1VertexSize = PMesh1->VertexSize;const EResourceList*  LMesh1VertexAttributes = &PMesh1->VertexAttributes;
			LMesh1AllVertices = E3dVertices_New(LMesh1VertexSize, PMesh1->Vertices.Count + LVN, FALSE);
			E3dVertices_Copy(PMesh1->Vertices.Items, LMesh1VertexSize, LMesh1VertexAttributes,  LMesh1AllVertices, LMesh1VertexSize, LMesh1VertexAttributes,  PMesh1->Vertices.Count);

			{
			  EIndex  LVI = 0;E3dVertex*  LVertex = EStruct_ByIndex(LMesh1AllVertices, LMesh1VertexSize, PMesh1->Vertices.Count);for(;LVI < LVN;LVI++, EStruct_Inc(LVertex, LMesh1VertexSize))
			  {
			  	E3dM_VertexInit(LVertex);
			  }
			}

			E3dVertex*  LVertex = EStruct_ByIndex(LMesh1AllVertices, LMesh1VertexSize, PMesh1->Vertices.Count);


//			E3dVertex*  LVertex = E3dMesh_VerticesAdd(PMesh1, LVN);

			if(LVertex)
			{
				EIndex  LVI = 0;const E3dBNewVertex*  LBNewVertex = PInfo->BNewVertices;for(;LVI < LVN;LVI++, LBNewVertex++, EStruct_Inc(LVertex, LMesh1VertexSize))
				{
					LMesh1ToDstMeshVertexIndexTable[LMesh1VerticesCountBeforeAddingIntersectedVertices + LVI] = LVI + LMesh0VerticesCountBeforeAddingIntersectedVertices;

					LVertex->Position = LBNewVertex->Position;

#if E3dDebugKEEP_VERTS_ON_MESHB
					EMatrix_Transform3x4_v(LMesh0_to_Mesh1Matrix, LVertex->Position);
#endif

//					LVertex->Flags |= E3dVtxSELECTED;
				}
			}
		}
//tm_POP();
	}




tm_PUSH("_MeshSetPolygonEdgeIndices");

// (C) Fill in Polygon Edge lists for trimmed Polygons
//
	_MeshSetPolygonEdgeIndices(PMesh0, LMesh0BPolygons);
	_MeshSetPolygonEdgeIndices(PMesh1, LMesh1BPolygons);
tm_POP();







#if E3dDEBUG
_Verbose = FALSE;
#endif

	{
		E3dPolygonSlice  LPolygonSplice;

		{
			EList_InitAN(LPolygonSplice.BVertexNodes, 256);

			EList_InitA(LPolygonSplice.EntryPoints);
			EList_InitA(LPolygonSplice.ExitPoints);

			LPolygonSplice.BNewVertices = PInfo->BNewVertices;LPolygonSplice.BNewVertices_Count = PInfo->BNewVertices_Count;

			EList_InitA(LPolygonSplice.NewEdges);
			LPolygonSplice.DstMesh = PDstMesh;
		}


		EIndex  LBadPolys = 0;


		if(PInfo->RemoveUnusedVertices)
		{
			EIndex  LVI = 0;const EIndex  LVN = PDstMesh->Vertices.Count;E3dVertex*  LVertex = PDstMesh->Vertices.Items;const E3dVertexSize  LVertexSize = PDstMesh->VertexSize;for(;LVI < LVN;LVI++, EStruct_Inc(LVertex, LVertexSize))
			{
				LVertex->Flags &= ~E3dVtxMARK0;
			}
		}

		EIndex  LTotalSourcePolygonsFailed = 0;

// Add Polygons of Mesh0 to DstMesh
//
#if E3dDEBUG
_Verbose = TRUE;
#endif

//		if(0)
		{
tm_PUSH("Add Polygons of Mesh0 to DstMesh");

			_OffsetVertices(PInfo, LMesh0VerticesCountBeforeAddingIntersectedVertices,  PMesh0,  LMesh0BEdges, LMesh0VerticesCountBeforeAddingIntersectedVertices);

			{
				LPolygonSplice.Mesh0BVertices = LMesh0BVertices;

				LPolygonSplice.SrcMesh = NULL;
				LPolygonSplice.SrcMesh_to_Mesh0Matrix = NULL;

				LPolygonSplice.Mesh1BVertices = LMesh1BVertices;
				LPolygonSplice.Mesh1Edges = PMesh1->Edges.Items;
				LPolygonSplice.Mesh1WingedEdges = PMesh1->WingedEdges;

				LPolygonSplice.Edges = PMesh0->Edges.Items;
				LPolygonSplice.BEdges = LMesh0BEdges;
				LPolygonSplice.SurfacePointMode = E3dON_MESHB_EDGE_MESHA_SURFACE;
				LPolygonSplice.AddEdges = TRUE;
				LPolygonSplice.InvertPolygon = FALSE;

// E3dFINISHME - figure out all NewPolyVertexMode HoleVertexMode settings here
//
				switch(LOperation)
				{
				  case E3dSUBTRACTION:   LPolygonSplice.InvertInOut = FALSE;LPolygonSplice.NewPolyVertexMode = 0;                             LPolygonSplice.HoleVertexMode = E3dON_MESHB_EDGE_MESHA_SURFACE;LPolygonSplice.KeepSurfaceTrimmedPoly = TRUE; break;
				  case E3dINTERSECTION:  LPolygonSplice.InvertInOut = TRUE; LPolygonSplice.NewPolyVertexMode = E3dON_MESHB_EDGE_MESHA_SURFACE;LPolygonSplice.HoleVertexMode = 0;                             LPolygonSplice.KeepSurfaceTrimmedPoly = FALSE;break;
				  case E3dUNION:         LPolygonSplice.InvertInOut = FALSE;LPolygonSplice.NewPolyVertexMode = 0;                             LPolygonSplice.HoleVertexMode = E3dON_MESHB_EDGE_MESHA_SURFACE;LPolygonSplice.KeepSurfaceTrimmedPoly = TRUE; break;
				}
			}


			EIndex  LMeshSourcePolygonsFailed = 0;
			EIndex  LPGI = 0;const EIndex  LPGN = PMesh0->PolyGroups.Count;for(;LPGI < LPGN;LPGI++)
			{
				E3dPolyGroup*  LOldPolyGroup = PMesh0->PolyGroups.Items[LPGI];
				E3dPolyGroup*  LNewPolyGroup = E3dPolyGroup_Copy(LOldPolyGroup, NULL,  NULL,  ECopyNAME,  E3d_Scene);if(LNewPolyGroup)
				{
					EIndex  LPolyGroupSourcePolygonsFailed = 0;

					const E3dVertexNodeSize  LVertexNodeSize = LOldPolyGroup->VertexNodeSize;
					const int                LVertexNodeNormalOffset = E3dVertexNodeAttribute_Offset(&LOldPolyGroup->VertexAttributes,  E3dVtxAttribute_Normal, 0);
					const E3dBPolygon*       LBPolygon = LMesh0BPolygons[LPGI];
					E3dMesh_AppendPolyGroup(PDstMesh, LNewPolyGroup);

					{
						EList_InitA_s(LPolygonSplice.NewPolygons);
						LPolygonSplice.BVertexNodes_Count = 0;

						LPolygonSplice.EntryPoints_Count = 0;
						LPolygonSplice.ExitPoints_Count = 0;

						LPolygonSplice.OldPolyGroup = LOldPolyGroup;
						LPolygonSplice.VertexNodeSize = LVertexNodeSize;
						LPolygonSplice.VertexNodeNormalOffset = LVertexNodeNormalOffset;

					}

					EIndex  LPI = 0;const EIndex  LPN = LOldPolyGroup->Polygons.Count;E3dPolygon*  LOldPolygon = LOldPolyGroup->Polygons.Items;for(;LPI < LPN;LPI++, LOldPolygon++, LBPolygon++)
//E3dPolygon*  LOldPolygon = LOldPolyGroup->Polygons;if(0)
					{
						LPolygonSplice.BPolygon = LBPolygon;
						if(LBPolygon->EdgeTrimmed || LBPolygon->SurfaceTrimmed)
						{
							if(_PolygonSlice(LOldPolygon, &LPolygonSplice, NULL) != E_SUCCESS) { LPolyGroupSourcePolygonsFailed++;if(_HighlightBadPolysInA) LOldPolygon->Flags |= E3dPolygonSELECTED; }
						}
						else
						{
// Polygon was not intersected.
// If it's inside Mesh1 and we're in SUBTRACT or UNION mode, remove it.
// Because the Polygon was not intersected, all of its Vertices are either inside or outside of the other Mesh, so it's sufficient to check only one Vertex.
//
							if(LOldPolygon->VertexNodes)
							{
								const EIndex       LVertexIndex = LOldPolygon->VertexNodes->VertexID;
								const E3dBVertex*  LBVertex = LMesh0BVertices + LVertexIndex;

//printf("%s() %s:%d  Poly %lld  Vtx %lld  %d\n", __FUNCTION__, __FILE__, __LINE__, (long long)LPI, (long long)LVertexIndex, LBVertex->State == E3dINSIDE);fflush(stdout);

								switch(LOperation)
								{
									case E3dSUBTRACTION:
									case E3dUNION:
										if(LBVertex->State != E3dINSIDE)
										{
											E3dPolygon*  LNewPolygon = EList_StructAddA_s(LPolygonSplice.NewPolygons, 256);
											E3d_PolygonCopy(LOldPolygon, LVertexNodeSize,  LNewPolygon);
										}
									break;

									case E3dINTERSECTION:
										if(LBVertex->State == E3dINSIDE)
										{
											E3dPolygon*  LNewPolygon = EList_StructAddA_s(LPolygonSplice.NewPolygons, 256);
											E3d_PolygonCopy(LOldPolygon, LVertexNodeSize,  LNewPolygon);
										}
									break;
								}
							}
							else
							{
printf("%s() %s:%d  %s<%s/%lld:%s> Polygon %lld has no VertexNodes%s\n", __FUNCTION__, __FILE__, __LINE__, ECodeYellow, PMesh0->Name, (long long)LPGI, LOldPolyGroup->Name, (long long)LPI, ECodeNormal);fflush(stdout);
							}
						}
					}
					LNewPolyGroup->Polygons = LPolygonSplice.NewPolygons;
					EList_InitA_s(LPolygonSplice.NewPolygons);

					LMeshSourcePolygonsFailed += LPolyGroupSourcePolygonsFailed;

					LTotalSourcePolygonsFailed += LPolyGroupSourcePolygonsFailed;
				}
			}

			PInfo->MeshAPolygonsFailed = LMeshSourcePolygonsFailed;
tm_POP();
		}


//{ static int  c = 0;c++;if(c > 1) goto Done; }


		const EIndex  LNewEdgesFromMesh0 = LPolygonSplice.NewEdges_Count;


// Add Polygons of Mesh1 to DstMesh
//
#if E3dDEBUG
_Verbose = TRUE;
#endif

//printf("%s() %s:%d  <%s> closed : %d\n", __FUNCTION__, __FILE__, __LINE__, PMesh0->Name, PMesh0Closed);fflush(stdout);
		if(!PInfo->CheckIfMesh0IsClosed || PMesh0Closed)
//	  if(0)
		{
tm_PUSH("Add Polygons of Mesh1 to DstMesh");

			_OffsetVertices(PInfo, LMesh1VerticesCountBeforeAddingIntersectedVertices - LMesh0VerticesCountBeforeAddingIntersectedVertices, PMesh1,  LMesh1BEdges, LMesh1VerticesCountBeforeAddingIntersectedVertices);

			const EBool  LInvertPolygons = LOperation == E3dSUBTRACTION ? TRUE : FALSE;

			{
				LPolygonSplice.Mesh0BVertices = LMesh1BVertices;

				LPolygonSplice.SrcMesh = PMesh1;
				LPolygonSplice.SrcMesh_to_Mesh0Matrix = PMesh1_to_Mesh0Matrix;

				LPolygonSplice.Mesh1BVertices = LMesh0BVertices;
				LPolygonSplice.Mesh1Edges = PMesh0->Edges.Items;
				LPolygonSplice.Mesh1WingedEdges = PMesh0->WingedEdges;

				LPolygonSplice.Edges = PMesh1->Edges.Items;
				LPolygonSplice.BEdges = LMesh1BEdges;

				LPolygonSplice.SurfacePointMode = E3dON_MESHA_EDGE_MESHB_SURFACE;

				LPolygonSplice.AddEdges = TRUE;
				LPolygonSplice.InvertPolygon = LInvertPolygons;

// E3dFINISHME - figure out all NewPolyVertexMode HoleVertexMode settings here
//
				switch(LOperation)
				{
				  case E3dSUBTRACTION:   LPolygonSplice.InvertInOut = TRUE; LPolygonSplice.NewPolyVertexMode = E3dON_MESHA_EDGE_MESHB_SURFACE;LPolygonSplice.HoleVertexMode = E3dON_MESHB_EDGE_MESHA_SURFACE;LPolygonSplice.KeepSurfaceTrimmedPoly = FALSE;break;
				  case E3dINTERSECTION:  LPolygonSplice.InvertInOut = TRUE; LPolygonSplice.NewPolyVertexMode = E3dON_MESHA_EDGE_MESHB_SURFACE;LPolygonSplice.HoleVertexMode = 0;                             LPolygonSplice.KeepSurfaceTrimmedPoly = FALSE;break;
				  case E3dUNION:         LPolygonSplice.InvertInOut = FALSE;LPolygonSplice.NewPolyVertexMode = 0;                             LPolygonSplice.HoleVertexMode = E3dON_MESHA_EDGE_MESHB_SURFACE;LPolygonSplice.KeepSurfaceTrimmedPoly = TRUE; break;
				}
			}

			EIndex  LMeshSourcePolygonsFailed = 0;
			EIndex  LPGI = 0;const EIndex  LPGN = PMesh1->PolyGroups.Count;for(;LPGI < LPGN;LPGI++)
//			EIndex  LPGI = 0;if(0)
			{
				E3dPolyGroup*  LOldPolyGroup = PMesh1->PolyGroups.Items[LPGI];
				E3dPolyGroup*  LNewPolyGroup = E3dPolyGroup_Copy(LOldPolyGroup, NULL,  NULL,  ECopyNAME,  E3d_Scene);if(LNewPolyGroup)
				{
					EIndex  LPolyGroupSourcePolygonsFailed = 0;

					const E3dVertexNodeSize  LVertexNodeSize = LOldPolyGroup->VertexNodeSize;
					const E3dVertexNodeSize  LVertexNodeNormalOffset = E3dVertexNodeAttribute_Offset(&LOldPolyGroup->VertexAttributes,  E3dVtxAttribute_Normal, 0);
					const E3dBPolygon*       LBPolygon = LMesh1BPolygons[LPGI];

#if E3dDebugKEEP_VERTS_ON_MESHB
					E3dMesh_AppendPolyGroup(PMesh1, LNewPolyGroup);
#else
					E3dMesh_AppendPolyGroup(PDstMesh, LNewPolyGroup);
#endif

					{
						LPolygonSplice.BVertexNodes_Count = 0;

						LPolygonSplice.EntryPoints_Count = 0;
						LPolygonSplice.ExitPoints_Count = 0;

						LPolygonSplice.OldPolyGroup = LOldPolyGroup;
						LPolygonSplice.VertexNodeSize = LVertexNodeSize;
						LPolygonSplice.VertexNodeNormalOffset = LVertexNodeNormalOffset;
					}

					EIndex  LPI = 0;const EIndex  LPN = LOldPolyGroup->Polygons.Count;E3dPolygon*  LOldPolygon = LOldPolyGroup->Polygons.Items;for(;LPI < LPN;LPI++, LOldPolygon++, LBPolygon++)
					{
						LPolygonSplice.BPolygon = LBPolygon;
						if(LBPolygon->EdgeTrimmed || LBPolygon->SurfaceTrimmed)
						{
#if E3dDebugKEEP_VERTS_ON_MESHB
							if(_PolygonSlice(LOldPolygon, &LPolygonSplice, NULL) != E_SUCCESS) { LPolyGroupSourcePolygonsFailed++;if(_HighlightBadPolysInA) LOldPolygon->Flags |= E3dPolygonSELECTED; }
#else
							if(_PolygonSlice(LOldPolygon, &LPolygonSplice, LMesh1ToDstMeshVertexIndexTable) != E_SUCCESS) { LPolyGroupSourcePolygonsFailed++;if(_HighlightBadPolysInA) LOldPolygon->Flags |= E3dPolygonSELECTED; }
#endif
						}
						else
						{
// Polygon was not intersected.
// If it's inside Mesh0 and we're in SUBTRACT or INTERSECTION mode, add it.
// Because the Polygon was not intersected, all of its Vertices are either inside or outside of the other Mesh, so it's sufficient to check only one Vertex.
//
							const EIndex       LVertexIndex = LOldPolygon->VertexNodes->VertexID;
							const E3dBVertex*  LBVertex = LMesh1BVertices + LVertexIndex;

							switch(LOperation)
							{
								case E3dSUBTRACTION:
								case E3dINTERSECTION:
									if(LBVertex->State == E3dINSIDE)
									{
										E3dPolygon*  LNewPolygon = EList_StructAddA_s(LPolygonSplice.NewPolygons, 256);
										E3d_PolygonCopy(LOldPolygon, LVertexNodeSize,  LNewPolygon);

#if !E3dDebugKEEP_VERTS_ON_MESHB
										if(_PolygonRemapVertexIndices(LNewPolygon, LVertexNodeSize, LMesh1ToDstMeshVertexIndexTable,  PMesh1, PMesh1_to_Mesh0Matrix, PDstMesh) == E_SUCCESS)
										{
										}
#endif
										if(LInvertPolygons) E3dPolygon_Invert(LNewPolygon, LVertexNodeSize, LVertexNodeNormalOffset);
									}
								break;

								case E3dUNION:
									if(LBVertex->State != E3dINSIDE)
									{
										E3dPolygon*  LNewPolygon = EList_StructAddA_s(LPolygonSplice.NewPolygons, 256);
										E3d_PolygonCopy(LOldPolygon, LVertexNodeSize,  LNewPolygon);
#if !E3dDebugKEEP_VERTS_ON_MESHB

										if(_PolygonRemapVertexIndices(LNewPolygon, LVertexNodeSize, LMesh1ToDstMeshVertexIndexTable,  PMesh1, PMesh1_to_Mesh0Matrix, PDstMesh) == E_SUCCESS)
										{
										}
#endif
										if(LInvertPolygons) E3dPolygon_Invert(LNewPolygon, LVertexNodeSize, LVertexNodeNormalOffset);
									}
								break;
							}

						}
					}


/*
					EIndex  LPC1 = 0;const EIndex  LPN1 = LPolygonSplice.NewPolygons_Count;E3dPolygon*  LNewPolygon = LPolygonSplice.NewPolygons;for(;LPC1 < LPN1;LPC1++)
					{
@*
#if !E3dDebugKEEP_VERTS_ON_MESHB
//printf("%s() %s:%d  LPGI %lld  Poly %lld\n", __FUNCTION__, __FILE__, __LINE__, (long long)LPGI, (long long)LPC1);fflush(stdout);

						if(_PolygonRemapVertexIndices(LNewPolygon, LVertexNodeSize, LMesh1ToDstMeshVertexIndexTable,  PMesh1, PMesh1_to_Mesh0Matrix, PDstMesh) == E_SUCCESS)
						{
							if(LOperation == E3dSUBTRACTION) E3dPolygon_Invert(LNewPolygon, LVertexNodeSize);
							LNewPolygon++;
						}
#else
@/
						if(LOperation == E3dSUBTRACTION) E3dPolygon_Invert(LNewPolygon, LVertexNodeSize);
						LNewPolygon++;
//#endif
					}

					const EIndex  LPN1Valid = LNewPolygon - LPolygonSplice.NewPolygons;

					LNewPolyGroup->Polygons       = LPolygonSplice.NewPolygons;
					LNewPolyGroup->Polygons_Count = LPN1Valid;

					if(LPN1Valid < LPN1)
					{
						E3dp_PrintMessage(ELogMESSAGE, 15000, "%d incorrect Polygon(s). Highlighted Edges in source Meshes show problems. Try moving source Meshes slightly.", LPN1 - LPN1Valid);
						LBadPolys += LPN1 - LPN1Valid;
					}
*/

					LNewPolyGroup->Polygons = LPolygonSplice.NewPolygons;
					EList_InitA_s(LPolygonSplice.NewPolygons);

					LMeshSourcePolygonsFailed += LPolyGroupSourcePolygonsFailed;

					LTotalSourcePolygonsFailed += LPolyGroupSourcePolygonsFailed;
				}
			}

			PInfo->MeshBPolygonsFailed = LMeshSourcePolygonsFailed;
tm_POP();
		}



		if(LTotalSourcePolygonsFailed)
		{
			const char*  LOpStr = NULL;
			const char   LSubStr[] = " sub ";
			const char   LUniStr[] = " uni ";
			const char   LIntStr[] = " int ";

			switch(LOperation)
			{
				case E3dSUBTRACTION:   LOpStr = LSubStr;break;
				case E3dINTERSECTION:  LOpStr = LIntStr;break;
				case E3dUNION:         LOpStr = LUniStr;break;
			}

			const E3dpPrefs*  LPrefs = &E3dp_Prefs;
			const uint32_t    LLabelRGBAp8Color     = Xe_ForegroundRGBA8pColor;
			const uint32_t    LErrorRGBAp8Color     = LPrefs->ErrorRGBAp8Color;
			const uint32_t    LOperationRGBAp8Color = LPrefs->OperationRGBAp8Color;

			E3dp_PrintCompoundMessageVa(ELogMESSAGE, 15000,  XeFONT, XeFontDEFAULT, 
			 XeTEXT, _ToolName, XeTEXT, ":",
			 E3dp_ObjectS(PMesh0->Name ? PMesh0->Name : ""),
			 XeTEXT, ":",
			 XeCOLOR_RGBA, LErrorRGBAp8Color,           XeFTEXT, "%lld", (long long)PInfo->MeshAPolygonsFailed,

			 XeCOLOR_RGBA, LOperationRGBAp8Color,       XeTEXT, LOpStr,

			 E3dp_ObjectS(PMesh1->Name ? PMesh1->Name : ""),
			 XeTEXT, ":",
			 XeCOLOR_RGBA, LErrorRGBAp8Color,           XeFTEXT, "%lld", (long long)PInfo->MeshBPolygonsFailed,

			 XeCOLOR_RGBA, LLabelRGBAp8Color,  XeTEXT, " Highlighted Polygons in source Meshes show problems. Try moving the sources.",
			 0);
		}


		if(LBadPolys)
		{
			const EIndex  LIdx = _EdgeEdgeIntersectShow(PMesh0, LMesh0BEdges,  PMesh1, LMesh1BEdges,  PInfo);if(LIdx > 0)
			{
				printf("%s() %s:%d  %sEdge-Edge intersections: %lld%s\n", __FUNCTION__, __FILE__, __LINE__, ECodeCyan, (long long)LIdx, ECodeNormal);fflush(stdout);
			}
		}

#if E3dDEBUG
_Verbose = FALSE;
#endif


		EFreeChk(LPolygonSplice.BVertexNodes);
		EList_FreeAChk(LPolygonSplice.EntryPoints);
		EList_FreeAChk(LPolygonSplice.ExitPoints);


#if E3dPRESERVE_EDGES
		E3dEdgeTransferStruct  LEdgeTransferStruct;memset(&LEdgeTransferStruct, 0, sizeof(E3dEdgeTransferStruct));


// Add Edges from Mesh0 to PDstMesh
//
		{
tm_PUSH("Add Edges from Mesh0 to PDstMesh");
			uint8_t  LState = 0;

			switch(LOperation)
			{
				case E3dSUBTRACTION:
				case E3dUNION:
					LState = E3dOUTSIDE;
				break;

				case E3dINTERSECTION:
					LState = E3dINSIDE;
				break;
			}

			_TransferEdges(PMesh0, LMesh0BVertices, LMesh0BEdges, LState,  NULL,                            TRUE,  &LEdgeTransferStruct,  PMesh1_to_Mesh0Matrix, PDstMesh);
tm_POP();
		}


// Add Edges from Mesh1 to PDstMesh
//
		{
tm_PUSH("Add Edges from Mesh1 to PDstMesh");
			uint8_t  LState = 0;

			switch(LOperation)
			{
				case E3dSUBTRACTION:
				case E3dINTERSECTION:
					LState = E3dINSIDE;
				break;

				case E3dUNION:
					LState = E3dOUTSIDE;
				break;
			}

			_TransferEdges(PMesh1, LMesh1BVertices, LMesh1BEdges, LState,  LMesh1ToDstMeshVertexIndexTable, TRUE,  &LEdgeTransferStruct,  PMesh1_to_Mesh0Matrix, PDstMesh);
tm_POP();
		}


//		E3dEdges_Sort(LEdgeTransferStruct.DstMeshEdges, LEdgeTransferStruct.DstMeshEdges_Count);


// This will provide a quick way to check if an Edge is already added.
// We only need to do this check for the Edges created by _PolygonSlice(), because the original Edges (even after slicing) of Mesh0 and Mesh1 are guaranteed to be unique.
//
		LEdgeTransferStruct.VertexLinks = E3dVertexLinks_FromEdges(LEdgeTransferStruct.DstMeshEdges, LEdgeTransferStruct.DstMeshEdges_Count,  PDstMesh->Vertices.Count, sizeof(E3dVertexLinkNew));
		LEdgeTransferStruct.VertexLinksStride = sizeof(E3dVertexLinkNew);

// Add Edges, created by _PolygonSlice(), to PDstMesh
//
//		if(0)
		{
tm_PUSH("Add Edges, created by _PolygonSlice(), to PDstMesh");
			const EIndex  LNO = LEdgeTransferStruct.DstMeshEdges_Count, LNN = LPolygonSplice.NewEdges_Count;
//printf("%s() %s:%d  old Edges %lld  new Edges %lld\n", __FUNCTION__, __FILE__, __LINE__, (long long)LNO, (long long)LNN);fflush(stdout);


// Remap new Edge Vertices from Mesh1
//
//			if(0)
			{
				EIndex  LEC = LNewEdgesFromMesh0;E3dEdge*  LEdge = LPolygonSplice.NewEdges + LNewEdgesFromMesh0;for(;LEC < LNN;LEC++, LEdge++)
				{
					LEdge->Start = LMesh1ToDstMeshVertexIndexTable[LEdge->Start];
					LEdge->End   = LMesh1ToDstMeshVertexIndexTable[LEdge->End];
				}
			}


			{
				EList_ResizeNA(LEdgeTransferStruct.DstMeshEdges, LNO + LNN);

				EIndex  LEC = 0;const E3dEdge*  LEdge = LPolygonSplice.NewEdges;E3dEdge*  LDstEdge = LEdgeTransferStruct.DstMeshEdges + LNO;for(;LEC < LNN;LEC++, LEdge++)
				{
					const EIndex  LStartV = LEdge->Start, LEndV = LEdge->End;

					uint8_t  LAdd = 0;

					if(E3dVertexLinks_AddEdge(LEdgeTransferStruct.VertexLinks, LEdgeTransferStruct.VertexLinksStride,  LStartV, LEndV,  LEC) == E_SUCCESS) LAdd++;
					if(E3dVertexLinks_AddEdge(LEdgeTransferStruct.VertexLinks, LEdgeTransferStruct.VertexLinksStride,  LEndV, LStartV,  LEC) == E_SUCCESS) LAdd++;
					if(LAdd) *LDstEdge++ = *LEdge;
/*
					else
					{
printf("%s() %s:%d  %sEdge %lld - %lld rejected (already in list)%s\n", __FUNCTION__, __FILE__, __LINE__, ECodeYellow, (long long)LStartV, (long long)LEndV, ECodeNormal);fflush(stdout);
					}
*/
				}
				LEdgeTransferStruct.DstMeshEdges_Count = LDstEdge - LEdgeTransferStruct.DstMeshEdges;
			}


/*
			{
				E3dEdges_Sort(LPolygonSplice.NewEdges, LPolygonSplice.NewEdges_Count);


if(0)
{
printf("%s() %s:%d LPolygonSplice.NewEdges:\n", __FUNCTION__, __FILE__, __LINE__);fflush(stdout);

	EIndex  LEC = 0;const E3dEdge*  LEdge = LPolygonSplice.NewEdges;for(;LEC < LNN;LEC++, LEdge++)
	{
printf(" %lld-%lld", (long long)LEdge->Start, (long long)LEdge->End);
if(LEdge->Flags & E3dEdgeSELECTED) printf(" - %sSELECTED%s", ECodeRed, ECodeNormal);
printf("\n");fflush(stdout);
	}
}

				EList_ResizeNA(LEdgeTransferStruct.DstMeshEdges, LNO + LNN);
				memcpy(LEdgeTransferStruct.DstMeshEdges + LNO, LPolygonSplice.NewEdges, sizeof(E3dEdge) * LNN);
			}
*/
tm_POP();
		}

#endif


#if E3dPRESERVE_EDGES
tm_PUSH("Set");

tm_PUSH("Edges");
		EList_ResizeA(LEdgeTransferStruct.DstMeshEdges);

		PDstMesh->Edges.Items = LEdgeTransferStruct.DstMeshEdges;
		PDstMesh->Edges.Count = PDstMesh->Edges.CountAllocated = LEdgeTransferStruct.DstMeshEdges_Count;

		E3dVertexLinks_Free(LEdgeTransferStruct.VertexLinks, PDstMesh->Vertices.Count, LEdgeTransferStruct.VertexLinksStride);

		E3dMesh_EdgesSort(PDstMesh, FALSE);

		if(PInfo->RemoveUnusedVertices)
		{
			{
				E3dVertex*  LVertices = PDstMesh->Vertices.Items;const E3dVertexSize  LVertexSize = PDstMesh->VertexSize;
				EIndex  LEC = 0;const EIndex  LEN = PDstMesh->Edges.Count;const E3dEdge*  LEdge = PDstMesh->Edges.Items;for(;LEC < LEN;LEC++, LEdge++)
				{
					{ E3dVertex*  LVertex = EStruct_ByIndex(LVertices, LVertexSize, LEdge->Start);LVertex->Flags |= E3dVtxMARK0; }
					{ E3dVertex*  LVertex = EStruct_ByIndex(LVertices, LVertexSize, LEdge->End);LVertex->Flags |= E3dVtxMARK0; }
				}
			}

			{
				EIndex        LNewVertices_Count = 0;
				const EIndex  LVN = PDstMesh->Vertices.Count;
				EIndex*  LVertexRemappingTable;if(LVN) ENewN(LVN, LVertexRemappingTable);else LVertexRemappingTable = NULL;

				{
					EIndex  LVI = 0;const E3dVertex*  LVertex = PDstMesh->Vertices.Items;const E3dVertexSize  LVertexSize = PDstMesh->VertexSize;for(;LVI < LVN;LVI++, EStruct_Inc(LVertex, LVertexSize))
					{
						if(LVertex->Flags & E3dVtxMARK0) LNewVertices_Count++;

//					if(LVertex->Flags & E3dVtxMARK0) LVertex->Flags &= ~E3dVtxSELECTED;
//					else LVertex->Flags |= E3dVtxSELECTED;
					}
				}

				if(LNewVertices_Count)
				{
					const E3dVertexSize  LVertexSize = PDstMesh->VertexSize;const EResourceList*  LVertexAttributes = &PDstMesh->VertexAttributes;
					E3dVertex*  LNewVertices = E3dVertices_New(LVertexSize, LNewVertices_Count,  FALSE);

					EIndex  LVI = 0, LNewVertexIndex = 0;const E3dVertex*  LOldVertex = PDstMesh->Vertices.Items;E3dVertex*  LNewVertex = LNewVertices;for(;LVI < LVN;LVI++, EStruct_Inc(LOldVertex, LVertexSize))
					{
						LVertexRemappingTable[LVI] = LNewVertexIndex;

						if(LOldVertex->Flags & E3dVtxMARK0)
						{
							E3dVertex_Copy(LOldVertex, LVertexSize, LVertexAttributes,  LNewVertex, LVertexSize, LVertexAttributes);
							EStruct_Inc(LNewVertex, LVertexSize);
							LNewVertexIndex++;
						}

//					if(LVertex->Flags & E3dVtxMARK0) LVertex->Flags &= ~E3dVtxSELECTED;
//					else LVertex->Flags |= E3dVtxSELECTED;
					}
					E3dMesh_VerticesFree(PDstMesh);
					PDstMesh->Vertices.Items = LNewVertices;
					PDstMesh->Vertices.Count = PDstMesh->Vertices.CountAllocated = LNewVertices_Count;
					E3dMesh_EdgesAndPolyGroupsRemapVertexIndices(PDstMesh, LVertexRemappingTable);
				}
				else E3dMesh_VerticesFree(PDstMesh);

				EFreeChk(LVertexRemappingTable);
			}
		}
tm_POP();

tm_PUSH("E3dMesh_PolyGroupsEdgesFromMeshEdges");
		E3dMesh_PolyGroupsEdgesFromMeshEdges(PDstMesh, TRUE, TRUE);
tm_POP();

tm_PUSH("E3dMesh_EdgeFlagsUpdate");
		E3dMesh_EdgeFlagsUpdate(PDstMesh);
tm_POP();

//printf("%s() %s:%d  %08x\n", __FUNCTION__, __FILE__, __LINE__, E3dUpdateALL & ~(E3dUpdateEDGES|E3dUpdateREMOVE_STRIPS|E3dUpdateTRIANGULATE));fflush(stdout);
tm_PUSH("E3dObject_UpdateForDisplay");
		E3dObject_UpdateForDisplay((E3dObject*)PDstMesh,  E3dUpdateALL & ~(E3dUpdateEDGES|E3dUpdateREMOVE_STRIPS|E3dUpdateTRIANGULATE), E3dCallALL & ~E3dCallOUTPUTS,  LScene);
tm_POP();

#else
		E3dObject_UpdateForDisplay((E3dObject*)PDstMesh,  E3dUpdateALL & ~(E3dUpdateREMOVE_STRIPS|E3dUpdateTRIANGULATE), E3dCallALL & ~E3dCallOUTPUTS,  LScene);
#endif

#if E3dDebugADDMESHBPOLYS
		E3dObject_UpdateForDisplay((E3dObject*)PMesh1,  E3dUpdateALL & ~(E3dUpdateTRIANGULATE), E3dCallALL & ~E3dCallOUTPUTS,  LScene);
#endif

		EList_FreeChk(LPolygonSplice.NewEdges);
tm_POP();
	}

// Clean-up
//
tm_PUSH("Clean-up");
	EFreeChk(LMesh0BVertices);
	EFreeChk(LMesh1BVertices);
	EFreeChk(LMesh1AllVertices);

	_BEdgesFree(LMesh0BEdges, PMesh0->Edges.Count);
	_BEdgesFree(LMesh1BEdges, LMesh1BEdges_Count);

	_BPolygonsFree(LMesh0BPolygons, LMesh0BPolygons_Count, PMesh0);
	_BPolygonsFree(LMesh1BPolygons, LMesh1BPolygons_Count, PMesh1);

	EList_FreeChk(PInfo->BNewVertices);

	EFree(LMesh1ToDstMeshVertexIndexTable);
tm_POP();



	if(PInfo->BNewVertices == 0)
	{
		E3dp_PrintMessage(ELogMESSAGE, 5000, "Boolean <%s> <%s> No intersections found", PMesh0->Name, PMesh1->Name);
		LResult = E_NOTHING_TO_DO;
	}

//Done:
tm_POP();

	return(LResult);
}


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

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

	return(E_SUCCESS);
}


//========================================
//========================================
static void _DCB_HideOriginals(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	_HideOriginals = ((XeToggleButtonCallbackStruct*)PCallData)->Set;
}


//========================================
//========================================
static void _DCB_SelectResult(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	_SelectResult = ((XeToggleButtonCallbackStruct*)PCallData)->Set;
}


//========================================
//========================================
static void _DCB_HideResult(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	_HideResult = ((XeToggleButtonCallbackStruct*)PCallData)->Set;
}


//========================================
// Settings dialog callback
//========================================
static void _DCB_Close(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	XeCallbackStruct*  LCBS = (XeCallbackStruct*)PCallData;
	if(LCBS->Reason == XeCR_PROTOCOLS)
	{
		XeWidget_Unmap(_DialogW);
		EPluginLock_Del(_Plugin, &_MainDialogBoxLock, FALSE);
	}
}


//================================================
// Pop up the options dialog box
//================================================
static void _PopupDialog(void)
{
	if(_MainDialogBoxLock == NULL) _MainDialogBoxLock = EPluginLock_Add(_Plugin, "BooleanSettings", _MainDialogBoxLockDel);

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

		EXeStart;
		EXeSetArg(XeNAllowShellResize, TRUE);
		EXeSetArg(XeNButtonsStretch, FALSE);
		EXeSetArg(XeNWorkAreaType, XeMATRIX);
		EXeSetArg(XeNWMDecorations, XeWMDecorCLOSE|XeWMDecorBORDER|XeWMDecorTITLE);
		EXeSetArg(XeNTitle, "Boolean options");EXeSetArg(XeNIconName, "Boolean");
		XeWidget  LDialogW = XeDialog_New(XeDIALOG_TEMPLATE, "Boolean", Eqx_TopShellW,  Args, ArgCnt);if(LDialogW == NULL) return;
		XeAddCallback(LDialogW, XeNWMCallback, _DCB_Close, (EPointer)0);

		_DialogW = LDialogW;

		XeWidget  LTopMatrixW = XeDialog_GetChild(LDialogW, XeDIALOG_WORK_AREA);

		EXeStart;
		EXeSetArg(XeNOrientation, XeVERTICAL);
		EXeSetArg(XeNMarginSizeX, 4);
		Xe_SetValues(LTopMatrixW, Args, ArgCnt);

		EXeStart;
		EXeSetArg(XeNShadowThickness, 0);
		EXeSetArg(XeNHighlightThickness, 0);
		EXeSetArg(XeNResizableY, FALSE);
		_HideOriginalsW = LW = XeWidget_ManagedNew("Hide originals", XeToggleButton_class, LTopMatrixW,  Args, ArgCnt);
		XeToggleButton_SetState(LW, _HideOriginals, FALSE);
		XeAddCallback(LW, XeNValueChangedCallback, _DCB_HideOriginals, (EPointer)NULL);

		_SelectResultW = LW = XeWidget_ManagedNew("Select result", XeToggleButton_class, LTopMatrixW,  Args, ArgCnt);
		XeAddCallback(LW, XeNValueChangedCallback, _DCB_SelectResult, (EPointer)NULL);
		XeToggleButton_SetState(LW, _SelectResult, FALSE);

		_HideResultW = LW = XeWidget_ManagedNew("Hide result", XeToggleButton_class, LTopMatrixW,  Args, ArgCnt);
		XeAddCallback(LW, XeNValueChangedCallback, _DCB_HideResult, (EPointer)NULL);
		XeToggleButton_SetState(LW, _HideResult, FALSE);

		XeShell_Raise(_DialogW);
		XeShell_CenterToWidget(_DialogW, Eqx_TopShellW);
	}
	else
	{
		XeToggleButton_SetState(_HideOriginalsW, _HideOriginals, FALSE);
		XeToggleButton_SetState(_SelectResultW,  _SelectResult,  FALSE);
		XeToggleButton_SetState(_HideResultW,    _HideResult,    FALSE);

		XeShell_Raise(_DialogW);
	}
}


//================================================================
// Callback for input changes
// This function should Not call any 3DWindow redrawing function,
// just return with TRUE if a redraw is needed!
//================================================================
static EChangeFlags _InputChanged(EInterface* PInterface, EInterfaceCallbackStruct* PCBS)
{
	E3dObject*    LObject  = (E3dObject*)PInterface->Object;
	E3dInfo*      LInfo    = (E3dInfo*)PInterface->ObjectComponent;
	EChangeFlags  LChanged = E3dObject_GetChangeFlagsByClass(LObject);

	LInfo->InputChangeCount++;

// Only respond if the Object is instantiated (e.g. not in the undo stack after being deleted)
//
	if(LObject->Models.Count > 0)
	{
		EChangeFlags  LChangesRequest = 0;
		EBool         LUpdate = FALSE;

		EIndex  LI = 0;const EIndex  LN = PCBS->Objects_Count;const EObjectChg*  LObjectChg = PCBS->Objects;for(;LI < LN;LI++, LObjectChg++)
		{
			if(LObjectChg->Changed & E3dChgMeshSHAPE)
			{
				LChangesRequest |= E3dChgMeshSHAPE;
				if(LInfo->ReactToShapeChange) LUpdate = TRUE;
			}
			if(LObjectChg->Changed & E3dChgMeshTRANSFORM)
			{
				LChangesRequest |= E3dChgMeshTRANSFORM;
				if(LInfo->ReactToTransformChange) LUpdate = TRUE;
			}
		}

printf("%s() %s:%d  LUpdate: %d\n", __FUNCTION__, __FILE__, __LINE__, LUpdate);fflush(stdout);
		if(LUpdate) E3dObject_CustomUpdate(LObject, LInfo,  PCBS->CallOutputs ? E3dCallALL : E3dCallALL & ~E3dCallOUTPUTS,  &LChanged);
		else LInfo->ChangeRequestsCached |= LChangesRequest;
	}

	return(LChanged);
}


//========================================
// Constructor for the ObjectComponent
//========================================
static int _Init(E3dObject* PObject, E3dObjectComponent* PInfo)
{
	E3dInfo*  LInfo = (E3dInfo*)PInfo;

	EClr(LInfo->Models);
	EClr(LInfo->Geometries);

	LInfo->Operation = 0;

	LInfo->ChangeRequestsCached = 0;
	LInfo->ReactToShapeChange = TRUE;LInfo->ReactToTransformChange = TRUE;

	LInfo->CheckIfMesh0IsClosed = FALSE;
	LInfo->RemoveUnusedVertices = TRUE;

	EList_InitA(LInfo->BNewVertices);

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

	E3dMergePolyGroups_Init(&LInfo->MergePolyGroups);

// Clear debugging info
//
	LInfo->MeshAPolygonsFailed = 0;
	LInfo->MeshBPolygonsFailed = 0;

	return(E_SUCCESS);
}


//========================================
// Copy-constructor
//========================================
static void _Copy(const E3dObject* PSObject, const E3dObjectComponent* PSInfo,  E3dObject* PDObject, E3dObjectComponent* PDInfo)
{
	const E3dInfo*  LSInfo = (E3dInfo*)PSInfo;
	E3dInfo*        LDInfo = (E3dInfo*)PDInfo;

	_Init(PDObject, PDInfo);

#define _CopyField(f)  LDInfo->f = LSInfo->f
	_CopyField(Operation);
	_CopyField(CheckIfMesh0IsClosed);
	_CopyField(RemoveUnusedVertices);
#undef _CopyField


	E_AssignToMember(LSInfo->Geometries[0], LDInfo,Geometries[0]);
	E_AssignToMember(LSInfo->Models[0], LDInfo,Models[0]);

//printf("%s%s() %s:%d  <%s>%s\n", ECodeCyan, __FUNCTION__, __FILE__, __LINE__, LSInfo->Geometries[1]->Name, ECodeNormal);fflush(stdout);

	E_AssignToMember(LSInfo->Geometries[1], LDInfo,Geometries[1]);
	E_AssignToMember(LSInfo->Models[1], LDInfo,Models[1]);
}


//========================================
// Destructor for the ObjectComponent
//========================================
static int _Free(E3dObject* PObject, E3dObjectComponent* PInfo)
{
	E3dInfo*  LInfo = (E3dInfo*)PInfo;

	if(LInfo->Geometries[0])
	{
		E3dBaseObject_RemoveOutput((E3dBaseObject*)LInfo->Geometries[0], &LInfo->InputInterface);
		E_Delete(LInfo->Geometries[0]);
	}
	if(LInfo->Models[0])
	{
		E3dBaseObject_RemoveOutput((E3dBaseObject*)LInfo->Models[0], &LInfo->InputInterface);
		E_Delete(LInfo->Models[0]);
	}

	if(LInfo->Geometries[1])
	{
		E3dBaseObject_RemoveOutput((E3dBaseObject*)LInfo->Geometries[1], &LInfo->InputInterface);
		E_Delete(LInfo->Geometries[1]);
	}
	if(LInfo->Models[1])
	{
		E3dBaseObject_RemoveOutput((E3dBaseObject*)LInfo->Models[1], &LInfo->InputInterface);
		E_Delete(LInfo->Models[1]);
	}

	EList_FreeA(LInfo->BNewVertices);

	EInterface_Clear(&LInfo->InputInterface);

	return(E_SUCCESS);
}


//========================================
// Update method
//========================================
static E3dObject* _Update1(E3dObject* PObject, E3dObjectComponent* PInfoV,  EUpdateFlags* PTriggeredUpdateFlags, EChangeFlags* PTriggeredSceneChangeFlags,  const EBool PCheckOnly)
{
	E3dInfo*  LInfo = NULL;
	EBool     LNewObject = FALSE;

	if(PObject == NULL)
	{
		E3dInfo*  LInfoIn = (E3dInfo*)PInfoV;

		if(LInfoIn->Geometries[0])
		{
			E3dMesh*  LMesh;E_New(E3dMesh, LMesh);
			PObject = (E3dObject*)LMesh;
			LNewObject = TRUE;
		}

		if(PObject)
		{
			LInfo = (E3dInfo*)E3dObjectComponent_Add(PObject, _ObjectComponent_class);
			if(PInfoV && PInfoV->Class == _ObjectComponent_class)
			{
				_Copy(PObject, PInfoV, PObject, (E3dObjectComponent*)LInfo);
			}

			if(LInfo->Geometries[0]) E3dBaseObject_AddOutput((E3dBaseObject*)LInfo->Geometries[0], &LInfo->InputInterface);
			if(LInfo->Models[0]) E3dBaseObject_AddOutput((E3dBaseObject*)LInfo->Models[0], &LInfo->InputInterface);

			if(LInfo->Geometries[1]) E3dBaseObject_AddOutput((E3dBaseObject*)LInfo->Geometries[1], &LInfo->InputInterface);
			if(LInfo->Models[1]) E3dBaseObject_AddOutput((E3dBaseObject*)LInfo->Models[1], &LInfo->InputInterface);
		}
	}
	else
	{
		LInfo = (E3dInfo*)E3dObjectComponent_ByClass(PObject, _ObjectComponent_class, FALSE);

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

// When a Scene is loaded, Update methods are called to create this link.
// The Geometry will be up-to-date, so if the link wasn't there yet, just exit.
// We should probably add a 3rd argument to this function ("Action=LINK"), so
// even input-less ComponentClasses don't rebuild their Geometry.
//
		if(PCheckOnly)
		{
			if(LInfo->Geometries[0] && !EObject_IsSubClass(LInfo->Geometries[0], E3dMesh)) { LInfo->Geometries[0] = NULL;return(NULL); }
			if(LInfo->Geometries[1] && !EObject_IsSubClass(LInfo->Geometries[1], E3dMesh)) { LInfo->Geometries[1] = NULL;return(NULL); }

//printf("%s%s() %s:%d%s CheckOnly\n", ECodeCyan, __FUNCTION__, __FILE__, __LINE__, ECodeNormal);fflush(stdout);
			if(LInfo->Geometries[0]) E3dBaseObject_AddOutput((E3dBaseObject*)LInfo->Geometries[0], &LInfo->InputInterface);
			if(LInfo->Models[0]) E3dBaseObject_AddOutput((E3dBaseObject*)LInfo->Models[0], &LInfo->InputInterface);

			if(LInfo->Geometries[1]) E3dBaseObject_AddOutput((E3dBaseObject*)LInfo->Geometries[1], &LInfo->InputInterface);
			if(LInfo->Models[1]) E3dBaseObject_AddOutput((E3dBaseObject*)LInfo->Models[1], &LInfo->InputInterface);

			return(PObject);
		}
	}

	if(PCheckOnly) return(NULL);

//{ static int  c = 0;c++; assert(c < 3); }

	if(PObject && LInfo && LInfo->Geometries[0] && LInfo->Geometries[1])
	{
		EBool        LProgressBarOn = FALSE;
		const ETime  LStartTime = E_GetMilliseconds();
		int          LProgressBarStep = 0;

#if PlTIMING >= 1
ETime  LStart = E_GetMicroseconds();
#endif

#if PlTIMING == 2
ETime  LPrev = LStart;
#endif

#if PlTIMING >= 3
ETimerStack_Init(&E3d_TimerStack);
#endif



tm_PUSH(PObject->Name);


//printf("%s%s() %s:%d%s  LInfo->BNewVertices_Count %lld\n", ECodeCyan, __FUNCTION__, __FILE__, __LINE__, ECodeNormal, (long long)LInfo->BNewVertices_Count);fflush(stdout);
		E3dMesh*   LDstMesh = (E3dMesh*)PObject;
		E3dMesh*   LMesh0 = (E3dMesh*)LInfo->Geometries[0];
		E3dModel*  LMesh0Model = LInfo->Models[0];
		E3dMesh*   LMesh1 = (E3dMesh*)LInfo->Geometries[1];
		E3dModel*  LMesh1Model = LInfo->Models[1];

//printf("%s() %s:%d  %p/%p  %p/%p\n", __FUNCTION__, __FILE__, __LINE__, LMesh0, LMesh0Model,  LMesh1, LMesh1Model);fflush(stdout);
		if(LMesh0 && LMesh0Model && LMesh1 && LMesh1Model)
		{
			E3dScene*  LScene = E3d_Scene;


tm_PUSH("Mesh0 WingedEdges");
			E3dMesh_CreateWingedEdgesFromEdges(LMesh0, FALSE, FALSE);  // Don't keep the vertex links for now (last FALSE) because adding Verts messes with it...
tm_POP();

			if(LMesh0->WingedEdges == NULL)
			{
printf("%s() %s:%d  Mesh-A <%s> %sCould not build WingedEdges%s\n", __FUNCTION__, __FILE__, __LINE__, LMesh0->Name, ECodeYellow, ECodeNormal);fflush(stdout);
				return(NULL);
			}


tm_PUSH("Mesh1 WingedEdges");
			E3dMesh_CreateWingedEdgesFromEdges(LMesh1, FALSE, FALSE);
tm_POP();

			if(LMesh1->WingedEdges == NULL)
			{
printf("%s() %s:%d  Mesh-B <%s> %sCould not build WingedEdges%s\n", __FUNCTION__, __FILE__, __LINE__, LMesh1->Name, ECodeYellow, ECodeNormal);fflush(stdout);
				return(NULL);
			}


			E3dMesh_FreeCaches(LDstMesh);
			E3dMesh_EdgesFree(LDstMesh);
			E3dMesh_PolyGroupsFree(LDstMesh);
			E3dMesh_VerticesFree(LDstMesh);

			E3dMatrix  LMesh1_to_Mesh0Matrix, LMesh1_to_Mesh0NormalMatrix;

			{
				E3dMatrix  LWorldToLocalMatrix0;

				E3dMatrix_Invert3x4(LMesh0Model->LocalToWorldMatrix, LWorldToLocalMatrix0);

//				E3dMatrix_Mult4x4_3(LWorldToLocalMatrix0, LMesh1Model->LocalToWorldMatrix, LMesh1_to_Mesh0Matrix);
				E3dMatrix_Mult4x4_3(LMesh1Model->LocalToWorldMatrix, LWorldToLocalMatrix0, LMesh1_to_Mesh0Matrix);

				E3dMatrix_TransposeAdjoint3x3(LMesh1_to_Mesh0Matrix, LMesh1_to_Mesh0NormalMatrix);
			}


/*
printf("%s() %s:%d  ModelA [%s%s%s]\n", __FUNCTION__, __FILE__, __LINE__, ECodeCyan, LInfo->Models[0]->Name, ECodeNormal);fflush(stdout);
E3dTransformSet_Print(&LInfo->Models[0]->TransformSet);

printf("%s() %s:%d  ModelB [%s%s%s]\n", __FUNCTION__, __FILE__, __LINE__, ECodeCyan, LInfo->Models[1]->Name, ECodeNormal);fflush(stdout);
E3dTransformSet_Print(&LInfo->Models[1]->TransformSet);
printf(" LMesh1_to_Mesh0Matrix\n");fflush(stdout);
E3dMatrix_Print4x4(LMesh1_to_Mesh0Matrix);
printf(" LMesh1_to_Mesh0NormalMatrix\n");fflush(stdout);
E3dMatrix_Print4x4(LMesh1_to_Mesh0NormalMatrix);
*/


			EBool  LMesh0Closed = TRUE;EIndex  LOpenEdges = 0;(void)LOpenEdges;

			{
//printf("%s() %s:%d <%s> ", __FUNCTION__, __FILE__, __LINE__, LMesh0->Name);fflush(stdout);
				EIndex  LEC = 0;const EIndex  LEN = LMesh0->Edges.Count;E3dEdge*  LEdge = LMesh0->Edges.Items;const E3dWingedEdge*  LWEdge = LMesh0->WingedEdges;for(;LEC < LEN;LEC++, LEdge++, LWEdge++)
				{
//printf(" %d", LWEdge->Polygons_Count);fflush(stdout);
					if(LWEdge->Polygons_Count != 2)
					{
						LMesh0Closed = FALSE;
//						break;
						LOpenEdges++;LEdge->Flags |= E3dEdgeSELECTED;
//						LWEdge->Polygons[0].Polygon->Flags |= E3dPolygonSELECTED;
					}
				}
//printf("%s() %s:%d  <%s> Open Edges : %lld\n", __FUNCTION__, __FILE__, __LINE__, LMesh0->Name, (long long)LOpenEdges);fflush(stdout);
//printf("\n");fflush(stdout);
			}


			EBool  LMesh1Closed = TRUE;


// Done with E3dMesh_CreateWingedEdgesFromEdges
//
			{
				if(!LProgressBarOn) { const ETime  LNow = E_GetMilliseconds();if(LNow - LStartTime > _PutUpProgressbarAfter_ms) { E3dProgressIndicator_SetMessage("%s: updating <%s>", _ToolName, PObject->Name ? PObject->Name : "");LProgressBarOn = TRUE; } }

				if(LProgressBarOn) { E3dProgressIndicator_Set((float)LProgressBarStep / _BooleanProgressbarSteps, NULL, (EPointer)NULL);XeApp_Sync(Xe_App); }
				LProgressBarStep++;
			}


			{
//printf("%s() %s:%d <%s> ", __FUNCTION__, __FILE__, __LINE__, LMesh0->Name);fflush(stdout);
			 EIndex  LEC = 0;const EIndex  LEN = LMesh1->Edges.Count;const E3dWingedEdge*  LWEdge = LMesh1->WingedEdges;for(;LEC < LEN;LEC++, LWEdge++)
				{
//printf(" %d", LWEdge->Polygons_Count);fflush(stdout);
					if(LWEdge->Polygons_Count != 2) { LMesh1Closed = FALSE;break; }
				}
//printf("\n");fflush(stdout);
			}
(void)LMesh1Closed;

//printf("%s() %s:%d  Mesh0 closed=%d   Mesh1 closed=%d\n", __FUNCTION__, __FILE__, __LINE__, LMesh0Closed, LMesh1Closed);fflush(stdout);


			EListAI_s(, E3dRPolyGroupPtr, LMesh0RPolyGroups);
			E3dAccelerator*  LMesh0Accelerator;E_NewAssignNoChkNoFree(E3dAccelerator, LMesh0Accelerator);

			{
				E3dGetRenderTrianglesStats  LStats;memset(&LStats, 0, sizeof(E3dGetRenderTrianglesStats));
tm_PUSH("Mesh0 E3dObject_GetRenderShapes()");
				E3dObject_GetRenderShapes((E3dObject*)LMesh0, NULL,  NULL, NULL,  E3dGrtDO_INVISIBLE|E3dGrtDO_WIREFRAME|E3dGrtPREP_FOR_RAYTRACING,  NULL, NULL,  &LMesh0RPolyGroups,  &LStats);
tm_POP();

tm_PUSH("Mesh0 E3dAccelerator_Build()");
//printf("%s() %s:%d %d\n", __FUNCTION__, __FILE__, __LINE__, E3dBOOLEAN_ACCELERATOR);fflush(stdout);
				E3dAccelerator_Build(LMesh0Accelerator,  &LMesh0RPolyGroups,  E3dBOOLEAN_ACCELERATOR);
tm_POP();

// Done with Mesh0's accelerator
//
				{
					if(!LProgressBarOn) { const ETime  LNow = E_GetMilliseconds();if(LNow - LStartTime > _PutUpProgressbarAfter_ms) { E3dProgressIndicator_SetMessage("%s: updating <%s>", _ToolName, PObject->Name ? PObject->Name : ""); LProgressBarOn = TRUE; } }

					if(LProgressBarOn) { E3dProgressIndicator_Set((float)LProgressBarStep / _BooleanProgressbarSteps, NULL, (EPointer)NULL);XeApp_Sync(Xe_App); }
					LProgressBarStep++;
				}

#if PlTIMING == 2
{ const ETime  LNow = E_GetMicroseconds();char  s[256];printf("%s() %s:%d  Mesh0 <%s> accelerator build : %s\n", __FUNCTION__, __FILE__, __LINE__, LMesh0->Name, EStr_MicrosecondsToString((LNow - LPrev), s, FALSE));fflush(stdout);LPrev = LNow; }
#endif
			}


			EListAI_s(, E3dRPolyGroupPtr, LMesh1RPolyGroups);
			E3dAccelerator*  LMesh1Accelerator;E_NewAssignNoChkNoFree(E3dAccelerator, LMesh1Accelerator);

			{
				E3dGetRenderTrianglesStats  LStats;memset(&LStats, 0, sizeof(E3dGetRenderTrianglesStats));
tm_PUSH("Mesh1 E3dObject_GetRenderShapes()");
//				E3dObject_GetRenderShapes((E3dObject*)LMesh1, NULL,  NULL, NULL,  E3dGrtDO_INVISIBLE|E3dGrtDO_WIREFRAME|E3dGrtPREP_FOR_RAYTRACING,  LMesh1_to_Mesh0Matrix, NULL,  &LMesh1RPolyGroups, &LMesh1RPolyGroups_Count,  &LStats);
				E3dObject_GetRenderShapes((E3dObject*)LMesh1, NULL,  NULL, NULL,  E3dGrtDO_INVISIBLE|E3dGrtDO_WIREFRAME|E3dGrtPREP_FOR_RAYTRACING,  LMesh1_to_Mesh0Matrix, LMesh1_to_Mesh0NormalMatrix,  &LMesh1RPolyGroups,  &LStats);
tm_POP();


tm_PUSH("Mesh1 E3dAccelerator_Build()");
				E3dAccelerator_Build(LMesh1Accelerator,  &LMesh1RPolyGroups,  E3dBOOLEAN_ACCELERATOR);
tm_POP();

// Done with Mesh1's accelerator
//
			{
				if(!LProgressBarOn) { const ETime  LNow = E_GetMilliseconds();if(LNow - LStartTime > _PutUpProgressbarAfter_ms) { E3dProgressIndicator_SetMessage("%s: updating <%s>", _ToolName, PObject->Name ? PObject->Name : "");LProgressBarOn = TRUE; } }

				if(LProgressBarOn) { E3dProgressIndicator_Set((float)LProgressBarStep / _BooleanProgressbarSteps, NULL, (EPointer)NULL);XeApp_Sync(Xe_App); }
				LProgressBarStep++;
			}

#if PlTIMING == 2
{ const ETime  LNow = E_GetMicroseconds();char  s[256];printf("%s() %s:%d  Mesh1 <%s> accelerator build : %s\n", __FUNCTION__, __FILE__, __LINE__, LMesh1->Name, EStr_MicrosecondsToString((LNow - LPrev), s, FALSE));fflush(stdout);LPrev = LNow; }
#endif
			}


//printf("%s() %s:%d  MeshA RPolyGroups <%s%s%s>\n", __FUNCTION__, __FILE__, __LINE__, ECodeCyan, LMesh0->Name, ECodeNormal);fflush(stdout);_RPolyGroupsPrint(LMesh0RPolyGroups, LMesh0RPolyGroups_Count);
//printf("%s() %s:%d  MeshB RPolyGroups <%s%s%s>\n", __FUNCTION__, __FILE__, __LINE__, ECodeCyan, LMesh1->Name, ECodeNormal);fflush(stdout);_RPolyGroupsPrint(LMesh1RPolyGroups, LMesh1RPolyGroups_Count);

			if(LDstMesh && LNewObject)
			{
				const size_t  LMesh0NameLen = LMesh0->Name ? strlen(LMesh0->Name) : 0;
				const size_t  LMesh1NameLen = LMesh1->Name ? strlen(LMesh1->Name) : 0;
				char          LName[LMesh0NameLen + 8 + LMesh1NameLen + 64];

				switch(LInfo->Operation)
				{
					case E3dSUBTRACTION:
						sprintf(LName, "%s_sub_%s", LMesh0->Name ? LMesh0->Name : "", LMesh1->Name ? LMesh1->Name : "");
					break;

					case E3dINTERSECTION:
						sprintf(LName, "%s_int_%s", LMesh0->Name ? LMesh0->Name : "", LMesh1->Name ? LMesh1->Name : "");
					break;

					case E3dUNION:
						sprintf(LName, "%s_uni_%s", LMesh0->Name ? LMesh0->Name : "", LMesh1->Name ? LMesh1->Name : "");
					break;

					default:
					break;
				}

				EObject_SetName(LDstMesh, E3dScene_GetUniqueObjectName(LScene, LName), FALSE);
			}

			if(_AddResultToAllOriginalModels && LNewObject)
			{
				EIndex  LI = 0;const EIndex  LN = LMesh0->Models.Count;for(;LI < LN;LI++)
				{
					E3dModel*  LModel = LMesh0->Models.Items[LI];
					if(LModel != LMesh0Model) E3dModel_ObjectAppend(LModel, (E3dObject*)LDstMesh, FALSE);
				}
			}


// Use VertexAttributes and VertexSize of Mesh0 for DstMesh
//
			const E3dVertexSize  LDstMeshVertexSize = LMesh0->VertexSize;
			EResourceList_Copy(&LMesh0->VertexAttributes, &LDstMesh->VertexAttributes,  EResSEMANTIC_STATIC | EResUI_STATIC, EResARGS);
			LDstMesh->VertexSize = LDstMeshVertexSize;

			E3dMesh_VerticesFree(LDstMesh);
			E3dMesh_VerticesAdd(LDstMesh, LMesh0->Vertices.Count);
			E3dVertices_Copy(LMesh0->Vertices.Items, LMesh0->VertexSize, &LMesh0->VertexAttributes,  LDstMesh->Vertices.Items, LDstMeshVertexSize, &LDstMesh->VertexAttributes,  LMesh0->Vertices.Count);

			switch(_Boolean(LInfo,  LMesh0, LMesh0Accelerator,  LMesh1, LMesh1Accelerator,  LMesh1_to_Mesh0Matrix, LMesh1_to_Mesh0NormalMatrix,  LDstMesh, LMesh0Closed))
			{
				case E_SUCCESS:
/*
					if(LNewObject)
					{
printf("%s() %s:%d\n", __FUNCTION__, __FILE__, __LINE__);fflush(stdout);
						E3dScene_ObjectsUpdateModelList(LScene);
						if(_HideOriginals)
						{
							LMesh0->Visible = FALSE;
							LMesh1->Visible = FALSE;
						}

						if(_SelectResult)
						{
printf("%s() %s:%d Models %p\n", __FUNCTION__, __FILE__, __LINE__, LDstMesh->Models);fflush(stdout);
							E3dSel_ObjectSelect(LScene, (E3dObject*)LDstMesh, TRUE, TRUE);
						}
						if(_HideResult) LDstMesh->Visible = FALSE;
					}
*/
				break;
			}


// Done with _Boolean()
//
			if(LInfo->MergePolyGroups.Active && LDstMesh->PolyGroups.Count > 1)
			{
				const EIndex  LNumOfPolyGroupsRemovedT = E3dMesh_MergePolyGroups(LDstMesh, &LInfo->MergePolyGroups);if(LNumOfPolyGroupsRemovedT)
				{
//				LRebuildPolyGroupEdges = TRUE;

//				LGeoUpdateFlags |= ~(E3dUpdateEDGES|E3dUpdateTRIANGULATE|E3dUpdateVERTEX_NORMAL);
//				LNumOfPolyGroupsRemoved += LNumOfPolyGroupsRemovedT;
				}
			}


			{
				if(!LProgressBarOn) { const ETime  LNow = E_GetMilliseconds();if(LNow - LStartTime > _PutUpProgressbarAfter_ms) { E3dProgressIndicator_SetMessage("%s: updating <%s>", _ToolName, PObject->Name ? PObject->Name : "");LProgressBarOn = TRUE; } }

				if(LProgressBarOn) { E3dProgressIndicator_Set((float)LProgressBarStep / _BooleanProgressbarSteps, NULL, (EPointer)NULL);XeApp_Sync(Xe_App); }
				LProgressBarStep++;
			}

//printf("%s() %s:%d  <%s> <%s>\n", __FUNCTION__, __FILE__, __LINE__, LMesh0->Name, LMesh1->Name);fflush(stdout);
			E_Delete(LMesh0Accelerator);EList_ObjectsFreeA_s(LMesh0RPolyGroups);
			E_Delete(LMesh1Accelerator);EList_ObjectsFreeA_s(LMesh1RPolyGroups);

			LInfo->ChangeRequestsCached = 0;
		}
tm_POP();


#if PlTIMING >= 3
ETimerStack_CollectProfilingInfo(&E3d_TimerStack, FALSE);
ETimerStack_OutputProfilingInfo(&E3d_TimerStack,  EStrCOLORS|EStrALIGN|EStrHEADER, stdout);
#endif

#if PlTIMING >= 1
{ const ETime  LNow = E_GetMicroseconds();char  s[256];printf("%s() %s:%d  DstMesh <%s>  Mesh0 <%s>  Mesh1 <%s> %s\n", __FUNCTION__, __FILE__, __LINE__, PObject->Name, LMesh0->Name, LMesh1->Name, EStr_MicrosecondsToString((LNow - LStart), s, FALSE));fflush(stdout); }
#endif


		if(LProgressBarOn) { E3dProgressIndicator_Set(-1.0, NULL, (EPointer)NULL);XeApp_Sync(Xe_App); }
	}

	LInfo->AttributeChangeCount = LInfo->InputChangeCount = 0;

	return(PObject);
}


typedef struct
{
	E3dModel*     Models[2];
	E3dGeometry*  Geometries[2];
	int           Operation;
	int16_t       InputID;
} _E3dTmp;


static void _Create(E3dModel* PModelA, E3dGeometry* PGeometryA,  E3dGeometry* PGeometryB, E3dModel* PModelB,  const _E3dTmp* PTmp)
{
	E3dScene*  LScene = E3d_Scene;

	E3dInfo  LTInfo;E3dObjectComponent_InitStatic(&LTInfo, _ObjectComponent_class);
	LTInfo.Operation = PTmp->Operation;
	E_Assign(PModelA, LTInfo.Models[0]);E_Assign(PGeometryA, LTInfo.Geometries[0]);
	E_Assign(PModelB, LTInfo.Models[1]);E_Assign(PGeometryB, LTInfo.Geometries[1]);

	E3dObject*  LNewObject = E3dObject_CustomNew(LScene, PModelA->Parent, PModelA, NULL, (E3dObjectComponent*)(&LTInfo), E3dNEW_INSTANCE, (E3dp_Prefs.Duplicate_Flags & E3dCopyOBJECT_UNIQUE_NAME) != 0,  NULL);if(LNewObject)
	{
//printf("%s() %s:%d  <%s%p%s:%s>\n", __FUNCTION__, __FILE__, __LINE__, ECodeCyan, LNewObject, ECodeNormal, LNewObject->Name);fflush(stdout);
		((E3dGeometry*)LNewObject)->AutoLOD = FALSE;

		E3dScene_ObjectsUpdateModelList(LScene);
		E3dScene_LayoutOnSchematics(LScene, TRUE);

		if(_HideOriginals)
		{
			if(PGeometryA) PGeometryA->Visible = FALSE;
			if(PGeometryB) PGeometryB->Visible = FALSE;
		}

		if(_SelectResult)
		{
			E3dSel_ObjectSelect(LScene, LNewObject, TRUE, TRUE);
		}
		if(_HideResult) LNewObject->Visible = FALSE;

		E3dWindows_RedrawByChangeFlags(LScene, E3dChgMeshSHAPE|E3dChgHIERARCHY);
	}
}


static _E3dTmp  _Tmp = { .InputID= -1 };

#include <E3D/UI/InputPick.h>

static void _RequestInputPick(E3dObjectClass* PObjectClass, E3dObject* PObject, const _E3dTmp* PTmp)
{
	int  LItemFlags = 0;

	if(E_IsSubClass(PObjectClass, E3dMesh)) LItemFlags |= E3dItemMESH;

	if(LItemFlags)
	{
		const E3dpPrefs*  LPrefs = &E3dp_Prefs;
		const uint32_t    LLabelRGBAp8Color = Xe_ForegroundRGBA8pColor;

		_E3dTmp*  LTmp = &_Tmp;

		LTmp->InputID = PTmp->InputID;
		LTmp->Operation = PTmp->Operation;

		const char*  LInputIDStr = NULL;
		switch(PTmp->InputID)
		{
			case 0:  LInputIDStr = "A";break;
			case 1:  LInputIDStr = "B";break;
		}

		E3dp_PickRequest(XeString_NewVa(TRUE,
		 XeFONT, XeFontDEFAULT,  XeCOLOR_RGBA, LLabelRGBAp8Color,  XeTEXT, _ToolName,  XeTEXT, ":    ",
		 E3dp_LMouseButtonS,     XeTEXT, ": pick a ", XeTEXT, PObjectClass->Name + 3,  XeTEXT, " as the new input-",  XeTEXT, LInputIDStr,
		 XeTEXT, "   ",  E3dp_RMouseButtonEscS,  XeTEXT, ": cancel",
		 0),
		 LItemFlags|E3dItemMODEL,  E3dPickCB_Input, PObject,  0, FALSE);
	}
}


//========================================
// Dialog box callbacks
//========================================
static void _DCB(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dScene*                LScene = E3d_Scene;
	E3dObjectPanel*          LPanel = (E3dObjectPanel*)PClientData;
	E3dObject*               LObject = LPanel->Object;
	E3dInfo*                 LInfo = (E3dInfo*)E3dObjectComponent_ByClass(LObject, _ObjectComponent_class, FALSE);
	XeComponentClassDrawer*  LDrawer = XeInfoPanel_GetFolderByClass((XeInfoPanel*)LPanel, _ObjectComponent_class);
	E3dResourcesGUI*         LGUI = (E3dResourcesGUI*)LDrawer->ResourcesGUI;

	if(PW == LGUI->MeshShowWs[0])
	{
		if(LInfo->Geometries[0])
		{
			EChangeFlags  LChanged = 0;
			if(E3dSel_ObjectSelect(E3d_Scene, (E3dObject*)LInfo->Geometries[0], FALSE, FALSE)) LChanged |= E3dChgSELECTION;
//		if(E3dSel_NodeSelect(E3d_Scene, (E3dNode*)LInfo->Models[0], FALSE, FALSE)) LChanged |= E3dChgSELECTION;

			E3dWindows_RedrawByChangeFlags(LScene, LChanged);
			E3dTransformPanel_Update(NULL, E3dDoALL, E3dDoALL, E3dDoALL,  E3dpDO_NOT_CHANGE,  E3dTrPanelALL);
			E3dp_PrintMessage(ELogMESSAGE, 3000, "%s: selected input Geometry-A <%s>", _ToolName, LInfo->Geometries[0]->Name);
		}
		else E3dp_PrintMessage(ELogMESSAGE, 3000, "%s: no input Geometry-A!", _ToolName);
	}
	else if(PW == LGUI->MeshPickWs[0])
	{
		_E3dTmp  LTmp = { .InputID= 0, .Operation= LInfo->Operation };
		_RequestInputPick(LObject->Class, LObject, &LTmp);
	}
	else if(PW == LGUI->MeshShowWs[1])
	{
		if(LInfo->Geometries[1])
		{
			EChangeFlags  LChanged = 0;
			if(E3dSel_ObjectSelect(E3d_Scene, (E3dObject*)LInfo->Geometries[1], FALSE, FALSE)) LChanged |= E3dChgSELECTION;
//		if(E3dSel_NodeSelect(E3d_Scene, (E3dNode*)LInfo->Models[1], FALSE, FALSE)) LChanged |= E3dChgSELECTION;

			E3dWindows_RedrawByChangeFlags(LScene, LChanged);
			E3dTransformPanel_Update(NULL,  E3dDoALL, E3dDoALL, E3dDoALL,  E3dpDO_NOT_CHANGE,  E3dTrPanelALL);
			E3dp_PrintMessage(ELogMESSAGE, 3000, "%s: selected input Geometry-B <%s>", _ToolName, LInfo->Geometries[1]->Name);
		}
		else E3dp_PrintMessage(ELogMESSAGE, 3000, "%s: no input Geometry-B!", _ToolName);
	}
	else if(PW == LGUI->MeshPickWs[1])
	{
		_E3dTmp  LTmp = { .InputID= 1, .Operation= LInfo->Operation };
		_RequestInputPick(LObject->Class, LObject, &LTmp);
	}
	else if(PW == LGUI->SubtractW)
	{
		if(LInfo->Operation != E3dSUBTRACTION)
		{
			LInfo->Operation = E3dSUBTRACTION;
			LInfo->AttributeChangeCount++;

			EChangeFlags  LChanged = E3dObject_GetChangeFlagsByClass(LObject);

			if(EObject_IsSubClass(LObject, E3dSpline))
			{
				E3dObject_CustomUpdate(LObject, LInfo,  0,  &LChanged);
				E3dSpline*  LSpline = (E3dSpline*)LObject;
				LChanged |= E3dSpline_CallOutputs(LSpline, E3dChgSplineSHAPE|E3dChgSplineCONNECTIVITY, FALSE);
			}
			else
			{
ETime  LPrev = E_GetMicroseconds();
				E3dObject_CustomUpdate(LObject, LInfo,  E3dCallOUTPUTS,  &LChanged);
{ const ETime  LNow = E_GetMicroseconds();char  s[256];printf("%s() %s:%d  <%s%s%s> %s\n", __FUNCTION__, __FILE__, __LINE__,  ECodeCyan, LObject->Name, ECodeNormal,  EStr_MicrosecondsToString((LNow - LPrev), s, FALSE));fflush(stdout);LPrev = LNow; }
			}

			E3dWindows_RedrawByChangeFlags(LScene, LChanged);
		}
	}
	else if(PW == LGUI->IntersectW)
	{
		if(LInfo->Operation != E3dINTERSECTION)
		{
			LInfo->Operation = E3dINTERSECTION;
			LInfo->AttributeChangeCount++;

			EChangeFlags  LChanged = E3dObject_GetChangeFlagsByClass(LObject);

			if(EObject_IsSubClass(LObject, E3dSpline))
			{
				E3dObject_CustomUpdate(LObject, LInfo,  0,  &LChanged);
				E3dSpline*  LSpline = (E3dSpline*)LObject;
				LChanged |= E3dSpline_CallOutputs(LSpline, E3dChgSplineSHAPE|E3dChgSplineCONNECTIVITY, FALSE);
			}
			else E3dObject_CustomUpdate(LObject, LInfo,  E3dCallOUTPUTS,  &LChanged);

			E3dWindows_RedrawByChangeFlags(LScene, LChanged);
		}
	}
	else if(PW == LGUI->UnionW)
	{
		if(LInfo->Operation != E3dUNION)
		{
			LInfo->Operation = E3dUNION;
			LInfo->AttributeChangeCount++;

			EChangeFlags  LChanged = E3dObject_GetChangeFlagsByClass(LObject);

			if(EObject_IsSubClass(LObject, E3dSpline))
			{
				E3dObject_CustomUpdate(LObject, LInfo,  0,  &LChanged);
				E3dSpline*  LSpline = (E3dSpline*)LObject;
				LChanged |= E3dSpline_CallOutputs(LSpline, E3dChgSplineSHAPE|E3dChgSplineCONNECTIVITY, FALSE);
			}
			else E3dObject_CustomUpdate(LObject, LInfo,  E3dCallOUTPUTS,  &LChanged);

			E3dWindows_RedrawByChangeFlags(LScene, LChanged);
		}
	}
}


static void _DCB_RemoveUnusedVertices(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dScene*        LScene = E3d_Scene;
	E3dObjectPanel*  LPanel = (E3dObjectPanel*)PClientData;
	E3dObject*       LObject = LPanel->Object;
	E3dInfo*         LInfo = (E3dInfo*)E3dObjectComponent_ByClass(LObject, _ObjectComponent_class, FALSE);

	LInfo->RemoveUnusedVertices = ((XeToggleButtonCallbackStruct*)PCallData)->Set;
	LInfo->AttributeChangeCount++;

	if(LInfo->ReactToShapeChange)
	{
		EChangeFlags  LChanged = E3dObject_GetChangeFlagsByClass(LObject);

		if(EObject_IsSubClass(LObject, E3dSpline))
		{
			E3dObject_CustomUpdate(LObject, LInfo,  0,  &LChanged);
			E3dSpline*  LSpline = (E3dSpline*)LObject;
			LChanged |= E3dSpline_CallOutputs(LSpline, E3dChgSplineSHAPE|E3dChgSplineCONNECTIVITY, FALSE);
		}
		else E3dObject_CustomUpdate(LObject, LInfo,  E3dCallOUTPUTS,  &LChanged);

		E3dWindows_RedrawByChangeFlags(LScene, LChanged);
	}
}


static void _DCB_ReactToShapeChange(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dScene*        LScene = E3d_Scene;
	E3dObjectPanel*  LPanel = (E3dObjectPanel*)PClientData;
	E3dObject*       LObject = LPanel->Object;
	E3dInfo*         LInfo = (E3dInfo*)E3dObjectComponent_ByClass(LObject, _ObjectComponent_class, FALSE);

	LInfo->ReactToShapeChange = ((XeToggleButtonCallbackStruct*)PCallData)->Set;

	if(LInfo->ReactToShapeChange)
	{
		if(LInfo->ChangeRequestsCached & E3dChgMeshSHAPE)
		{
			LInfo->ChangeRequestsCached &= !E3dChgMeshSHAPE;
			EChangeFlags  LChanged = E3dObject_GetChangeFlagsByClass(LObject);

			LInfo->InputChangeCount++;
			if(EObject_IsSubClass(LObject, E3dSpline))
			{
				E3dObject_CustomUpdate(LObject, LInfo,  0,  &LChanged);
				E3dSpline*  LSpline = (E3dSpline*)LObject;
				LChanged |= E3dSpline_CallOutputs(LSpline, E3dChgSplineSHAPE|E3dChgSplineCONNECTIVITY, FALSE);
			}
			else E3dObject_CustomUpdate(LObject, LInfo,  E3dCallOUTPUTS,  &LChanged);

			E3dWindows_RedrawByChangeFlags(LScene, LChanged);
		}
	}
}


static void _DCB_ReactToTransformChange(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dScene*        LScene = E3d_Scene;
	E3dObjectPanel*  LPanel = (E3dObjectPanel*)PClientData;
	E3dObject*       LObject = LPanel->Object;
	E3dInfo*         LInfo = (E3dInfo*)E3dObjectComponent_ByClass(LObject, _ObjectComponent_class, FALSE);

	LInfo->ReactToTransformChange = ((XeToggleButtonCallbackStruct*)PCallData)->Set;

	if(LInfo->ReactToTransformChange)
	{
		if(LInfo->ChangeRequestsCached & E3dChgMeshTRANSFORM)
		{
			LInfo->ChangeRequestsCached &= !E3dChgMeshTRANSFORM;
			EChangeFlags  LChanged = E3dObject_GetChangeFlagsByClass(LObject);

			LInfo->InputChangeCount++;
			if(EObject_IsSubClass(LObject, E3dSpline))
			{
				E3dObject_CustomUpdate(LObject, LInfo,  0,  &LChanged);
				E3dSpline*  LSpline = (E3dSpline*)LObject;
				LChanged |= E3dSpline_CallOutputs(LSpline, E3dChgSplineSHAPE|E3dChgSplineCONNECTIVITY, FALSE);
			}
			else E3dObject_CustomUpdate(LObject, LInfo,  E3dCallOUTPUTS,  &LChanged);

			E3dWindows_RedrawByChangeFlags(LScene, LChanged);
		}
	}
}


//========================================================
// Set the "Merge PolyGroups" option
//========================================================
static void _DCB_MergePolyGroups(EPointer PClientData, E3dMergePolyGroupsCallbackStruct* PCBS)
{
	E3dScene*        LScene = E3d_Scene;
	E3dObjectPanel*  LPanel = (E3dObjectPanel*)PClientData;
	E3dObject*       LObject = LPanel->Object;
	E3dInfo*         LInfo = (E3dInfo*)E3dObjectComponent_ByClass(LObject, _ObjectComponent_class, FALSE);
	EChangeFlags     LChanged = 0;

	E3dMergePolyGroups*  LMPG = &LInfo->MergePolyGroups;

	switch(PCBS->Reason)
	{
		case E3dMpgON:
		case E3dMpgOFF:
			{
				const EBool  LActive = PCBS->Reason == E3dMpgON ? TRUE : FALSE;

				LInfo->MergePolyGroups.Active = LActive;

				LChanged |= E3dObject_GetChangeFlagsByClass(LObject);
				LInfo->AttributeChangeCount++;
				E3dObject_CustomUpdate(LObject, LInfo,  E3dCallOUTPUTS,  &LChanged);

				if(E3dp_Prefs.Schematics_ShowPolyGroups)
				{
					E3dScene_LayoutOnSchematics(LScene, TRUE);
					LChanged |= E3dChgSCHEMATICS;
				}
			}
		break;

		case E3dMpgLAYOUT:
			{
				E3dMergePolyGroupsLayoutComponent*  LComponent = (E3dMergePolyGroupsLayoutComponent*)((uint8_t*)LMPG + PCBS->FieldOffset);
				LComponent->Mode = PCBS->Value.i32;

//				XeComponentClassDrawer*  LDrawer = XeInfoPanel_GetFolderByClass((XeInfoPanel*)LPanel, _ObjectComponent_class);
//				E3dResourcesGUI*         LGUI = (E3dResourcesGUI*)LDrawer->ResourcesGUI;_GUIUpdate(LInfo, LGUI);

				LInfo->AttributeChangeCount++;
				LChanged = E3dObject_GetChangeFlagsByClass(LObject);
				E3dObject_CustomUpdate(LObject, LInfo,  E3dCallOUTPUTS,  &LChanged);
			}
		break;

		case E3dMpgSPACING:
			{
				E3dMergePolyGroupsLayoutComponent*  LComponent = (E3dMergePolyGroupsLayoutComponent*)((uint8_t*)LMPG + PCBS->FieldOffset);
				LComponent->Spacing = PCBS->Value.f32;

				LChanged = E3dObject_GetChangeFlagsByClass(LObject);

				LInfo->AttributeChangeCount++;
				E3dObject_CustomUpdate(LObject, LInfo,  E3dCallOUTPUTS,  &LChanged);
			}
		break;
	}

	E3dWindows_RedrawByChangeFlags(LScene, LChanged);
}


#if E3dCHECKMESH0CLOSED
static void _DCB_CheckIfMesh0IsClosed(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dScene*        LScene = E3d_Scene;
	E3dObjectPanel*  LPanel = (E3dObjectPanel*)PClientData;
	E3dObject*       LObject = LPanel->Object;
	E3dInfo*         LInfo = (E3dInfo*)E3dObjectComponent_ByClass(LObject, _ObjectComponent_class, FALSE);

	LInfo->CheckIfMesh0IsClosed = ((XeToggleButtonCallbackStruct*)PCallData)->Set;

	EChangeFlags  LChanged = E3dObject_GetChangeFlagsByClass(LObject);

	if(EObject_IsSubClass(LObject, E3dSpline))
	{
		E3dObject_CustomUpdate(LObject, LInfo,  0,  &LChanged);
		E3dSpline*  LSpline = (E3dSpline*)LObject;
		LChanged |= E3dSpline_CallOutputs(LSpline, E3dChgSplineSHAPE|E3dChgSplineCONNECTIVITY, FALSE);
	}
	else E3dObject_CustomUpdate(LObject, LInfo,  E3dCallOUTPUTS,  &LChanged);

	E3dWindows_RedrawByChangeFlags(LScene, LChanged);
}
#endif


static void _StatusLabelUpdate(E3dInfo* PInfo, E3dResourcesGUI* PResourcesGUI)
{
	XeString  LXeString;

	const E3dpPrefs*  LPrefs = &E3dp_Prefs;

	if(PInfo->MeshAPolygonsFailed && !PInfo->MeshBPolygonsFailed)      LXeString = XeString_NewVa(TRUE, XeFONT, XeFontDEFAULT,  XeCOLOR_RGBA, LPrefs->ErrorRGBAp8Color,  XeFTEXT, "Failed source Polygons:  A: %lld", (long long)PInfo->MeshAPolygonsFailed,  0);
	else if(!PInfo->MeshAPolygonsFailed && PInfo->MeshBPolygonsFailed) LXeString = XeString_NewVa(TRUE, XeFONT, XeFontDEFAULT,  XeCOLOR_RGBA, LPrefs->ErrorRGBAp8Color,  XeFTEXT, "Failed source Polygons:  B %lld", (long long)PInfo->MeshBPolygonsFailed,  0);
	else if(PInfo->MeshAPolygonsFailed && PInfo->MeshBPolygonsFailed)  LXeString = XeString_NewVa(TRUE, XeFONT, XeFontDEFAULT,  XeCOLOR_RGBA, LPrefs->ErrorRGBAp8Color,  XeFTEXT, "Failed source Polygons:  A %lld  B %lld", (long long)PInfo->MeshAPolygonsFailed, (long long)PInfo->MeshAPolygonsFailed,  0);
	else LXeString = XeString_NewVa(TRUE, XeFONT, XeFontDEFAULT,  XeCOLOR_RGBA, LPrefs->YRGBAp8Color,  XeTEXT, "OK",  0);

	Xe_SetValuesVa(PResourcesGUI->StatusLabelW,  XeNLabelString, LXeString,  NULL);
}


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

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

#if E3dCHECKMESH0CLOSED
				XeToggleButton_SetState(LGUI->CheckIfMesh0IsClosedW,   LInfo->CheckIfMesh0IsClosed,   FALSE);
#endif
				XeToggleButton_SetState(LGUI->RemoveUnusedVerticesW,   LInfo->RemoveUnusedVertices,   FALSE);
				XeToggleButton_SetState(LGUI->ReactToShapeChangeW,     LInfo->ReactToShapeChange,     FALSE);
				XeToggleButton_SetState(LGUI->ReactToTransformChangeW, LInfo->ReactToTransformChange, FALSE);

				XeToggleButton_SetState(LGUI->SubtractW,  LInfo->Operation == E3dSUBTRACTION,  FALSE);
				XeToggleButton_SetState(LGUI->IntersectW, LInfo->Operation == E3dINTERSECTION, FALSE);
				XeToggleButton_SetState(LGUI->UnionW,     LInfo->Operation == E3dUNION,        FALSE);

				_StatusLabelUpdate(LInfo, LGUI);

				E3dMergePolyGroups_GUIUpdate(&LGUI->MergePolyGroups, &LInfo->MergePolyGroups, TRUE);
			}
			else
			{
				XeWidget  LTopMatrixW = LDrawer->Drawer.DrawerW, LW;
/*
				XeWidget  LIndentWs[7];int  LIndentN;       // For indenting the Scale Widgets
				XeWidget  LTraverseWs[16];int  LTraverseN;  // For TextField traversal
*/
				EArg      Args[256];int  ArgCnt;

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

// Inputs
//
				{
					EXeStart;
					EXeSetArg(XeNOrientation, XeHORIZONTAL);
					EXeSetArg(XeNMarginSizeY, 5);EXeSetArg(XeNMarginRight, 4);
					EXeSetArg(XeNSpacingX, 0);EXeSetArg(XeNSpacingY, 0);
					EXeSetArg(XeNColumns, 4);
					EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNAlignmentX, XeAlignBEGINNING);
					EXeSetArg(XeNResizableY, FALSE);
					XeWidget  LMatrixW = XeWidget_ManagedNew("", XeMatrixWidget_class, LTopMatrixW, Args, ArgCnt);


					EXeStart;
					EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNAlignmentX, XeAlignBEGINNING);
					EXeSetArg(XeNMarginSizeX, 2);EXeSetArg(XeNMarginSizeY, 3);
					EXeSetArg(XeNResizableY, FALSE);
					XeWidget_ManagedNew("Mesh-A", XeLabelWidget_class, LMatrixW,  Args, ArgCnt);

					EXeStart;
					EXeSetArg(XeNMarginSizeX, 0);EXeSetArg(XeNSpacingLeft, 6);
					EXeSetArg(XeNShadowThickness, 0);EXeSetArg(XeNLabelType, XeIMAGE);EXeSetArg(XeNImageSizeYPtr, &Xe_IconSize);EXeSetImages2(_Xe_Theme->Image_Find);
					EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNAlignmentX, XeAlignBEGINNING);
					EXeSetArg(XeNResizableY, FALSE);
					XeToolTip8("Select Mesh-A (so you can see it).");
					LGUI->MeshShowWs[0] = LW = XeWidget_ManagedNew("Show", XePushButton_class, LMatrixW,  Args, ArgCnt);
					XeAddCallback(LW, XeNActivateCallback, _DCB, (EPointer)LPanel);

					EXeStart;
					EXeSetArg(XeNMarginSizeX, 0);
					EXeSetArg(XeNShadowThickness, 0);EXeSetArg(XeNLabelType, XeIMAGE);EXeSetArg(XeNImageSizeYPtr, &Xe_IconSize);EXeSetImages2(_Xe_Theme->Image_Replace);
					EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNAlignmentX, XeAlignBEGINNING);
					EXeSetArg(XeNResizableY, FALSE);
					XeToolTip8("Pick new Mesh-A.");
					LGUI->MeshPickWs[0] = LW = XeWidget_ManagedNew("Pick", XePushButton_class, LMatrixW,  Args, ArgCnt);
					XeAddCallback(LW, XeNActivateCallback, _DCB, (EPointer)LPanel);

					EXeStart;
					EXeSetArg(XeNSpacingLeft, 4);
					EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNAlignmentX, XeAlignBEGINNING);
					LGUI->MeshNameLabelWs[0] = XeWidget_ManagedNew("", XeLabelWidget_class, LMatrixW,  Args, ArgCnt);


					EXeStart;
					EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNAlignmentX, XeAlignBEGINNING);
					EXeSetArg(XeNMarginSizeX, 2);EXeSetArg(XeNMarginSizeY, 3);
					EXeSetArg(XeNResizableY, FALSE);
					XeWidget_ManagedNew("Mesh-B", XeLabelWidget_class, LMatrixW,  Args, ArgCnt);

					EXeStart;
					EXeSetArg(XeNMarginSizeX, 0);EXeSetArg(XeNSpacingLeft, 6);
					EXeSetArg(XeNShadowThickness, 0);EXeSetArg(XeNLabelType, XeIMAGE);EXeSetArg(XeNImageSizeYPtr, &Xe_IconSize);EXeSetImages2(_Xe_Theme->Image_Find);
					EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNAlignmentX, XeAlignBEGINNING);
					EXeSetArg(XeNResizableY, FALSE);
					XeToolTip8("Select Mesh-B (so you can see it).");
					LGUI->MeshShowWs[1] = LW = XeWidget_ManagedNew("Show", XePushButton_class, LMatrixW,  Args, ArgCnt);
					XeAddCallback(LW, XeNActivateCallback, _DCB, (EPointer)LPanel);

					EXeStart;
					EXeSetArg(XeNMarginSizeX, 0);
					EXeSetArg(XeNShadowThickness, 0);EXeSetArg(XeNLabelType, XeIMAGE);EXeSetArg(XeNImageSizeYPtr, &Xe_IconSize);EXeSetImages2(_Xe_Theme->Image_Replace);
					EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNAlignmentX, XeAlignBEGINNING);
					EXeSetArg(XeNResizableY, FALSE);
					XeToolTip8("Pick new Mesh-B.");
					LGUI->MeshPickWs[1] = LW = XeWidget_ManagedNew("Pick", XePushButton_class, LMatrixW,  Args, ArgCnt);
					XeAddCallback(LW, XeNActivateCallback, _DCB, (EPointer)LPanel);

					EXeStart;
					EXeSetArg(XeNSpacingLeft, 4);
					EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNAlignmentX, XeAlignBEGINNING);
					LGUI->MeshNameLabelWs[1] = XeWidget_ManagedNew("", XeLabelWidget_class, LMatrixW,  Args, ArgCnt);
				}

// Operation
//
				{
					EXeStart;
					EXeSetArg(XeNRadioBehavior, TRUE);
					EXeSetArg(XeNSpacingLeft, 2);EXeSetArg(XeNSpacingRight, 8);
					EXeSetArg(XeNSpacingX, 8);
					EXeSetArg(XeNAdjustEntrySizesX, FALSE);
					EXeSetArg(XeNAdjustEntrySizesY, FALSE);
					XeWidget  LRadioMatrixW = XeWidget_ManagedNew("MatrixW", XeMatrixWidget_class, LTopMatrixW,  Args, ArgCnt);

					XeWidget_ManagedNew("Operation", XeLabelWidget_class, LRadioMatrixW,  NULL, 0);

					EXeStart;
					EXeSetArg(XeNHighlightThickness, 0);
					EXeSetArg(XeNSet, LInfo->Operation == E3dSUBTRACTION);
					EXeSetArg(XeNResizableY, FALSE);
					LGUI->SubtractW = LW = XeWidget_ManagedNew("Subtract", XeToggleButton_class, LRadioMatrixW,  Args, ArgCnt);
					XeAddCallback(LW, XeNValueChangedCallback, _DCB, (EPointer)LPanel);

					EXeStart;
					EXeSetArg(XeNHighlightThickness, 0);
					EXeSetArg(XeNSet, LInfo->Operation == E3dINTERSECTION);
					EXeSetArg(XeNResizableY, FALSE);
					LGUI->IntersectW = LW = XeWidget_ManagedNew("Intersect", XeToggleButton_class, LRadioMatrixW,  Args, ArgCnt);
					XeAddCallback(LW, XeNValueChangedCallback, _DCB, (EPointer)LPanel);

					EXeStart;
					EXeSetArg(XeNHighlightThickness, 0);
					EXeSetArg(XeNSet, LInfo->Operation == E3dUNION);
					EXeSetArg(XeNResizableY, FALSE);
					LGUI->UnionW = LW = XeWidget_ManagedNew("Union", XeToggleButton_class, LRadioMatrixW,  Args, ArgCnt);
					XeAddCallback(LW, XeNValueChangedCallback, _DCB, (EPointer)LPanel);
				}


#if E3dCHECKMESH0CLOSED
				EXeStart;
				EXeSetArg(XeNSpacingLeft, 2);EXeSetArg(XeNSpacingTop, 5);
				EXeSetArg(XeNShadowThickness, 0);
				EXeSetArg(XeNHighlightThickness, 0);
				EXeSetArg(XeNSet, LInfo->CheckIfMesh0IsClosed);
				EXeSetArg(XeNResizableY, FALSE);
				XeToolTip8("Check if the first Mesh is closed.");
				LGUI->CheckIfMesh0IsClosedW = LW = XeWidget_ManagedNew("Check Mesh-A", XeToggleButton_class, LTopMatrixW,  Args, ArgCnt);
				XeAddCallback(LW, XeNValueChangedCallback, _DCB_CheckIfMesh0IsClosed, (EPointer)LPanel);
#endif

				EXeStart;
				EXeSetArg(XeNSpacingLeft, 2);
#if E3dCHECKMESH0CLOSED == 0
				EXeSetArg(XeNSpacingTop, 5);
#endif
				EXeSetArg(XeNShadowThickness, 0);
				EXeSetArg(XeNHighlightThickness, 0);
				EXeSetArg(XeNSet, LInfo->RemoveUnusedVertices);
				EXeSetArg(XeNResizableY, FALSE);
				XeToolTip8("Remove unused Vertices from the resulting Mesh.");
				LGUI->RemoveUnusedVerticesW = LW = XeWidget_ManagedNew("Remove unused Vertices", XeToggleButton_class, LTopMatrixW,  Args, ArgCnt);
				XeAddCallback(LW, XeNValueChangedCallback, _DCB_RemoveUnusedVertices, (EPointer)LPanel);

				EXeStart;
				EXeSetArg(XeNSpacingLeft, 2);
				EXeSetArg(XeNShadowThickness, 0);
				EXeSetArg(XeNHighlightThickness, 0);
				EXeSetArg(XeNSet, LInfo->ReactToShapeChange);
				EXeSetArg(XeNResizableY, FALSE);
				XeToolTip8("Rebuild result if the shape of any of the inputs changes.");
				LGUI->ReactToShapeChangeW = LW = XeWidget_ManagedNew("React to shape change", XeToggleButton_class, LTopMatrixW,  Args, ArgCnt);
				XeAddCallback(LW, XeNValueChangedCallback, _DCB_ReactToShapeChange, (EPointer)LPanel);

				EXeStart;
				EXeSetArg(XeNSpacingLeft, 2);
				EXeSetArg(XeNShadowThickness, 0);
				EXeSetArg(XeNHighlightThickness, 0);
				EXeSetArg(XeNSet, LInfo->ReactToTransformChange);
				EXeSetArg(XeNResizableY, FALSE);
				XeToolTip8("Rebuild result if any of the inputs are moved.");
				LGUI->ReactToTransformChangeW = LW = XeWidget_ManagedNew("React to transform change", XeToggleButton_class, LTopMatrixW,  Args, ArgCnt);
				XeAddCallback(LW, XeNValueChangedCallback, _DCB_ReactToTransformChange, (EPointer)LPanel);

				E3dMergePolyGroups_GUICreate(&LGUI->MergePolyGroups, LTopMatrixW, 0, &LInfo->MergePolyGroups,  _DCB_MergePolyGroups, (EPointer)LPanel);
				E3dMergePolyGroups_GUIUpdate(&LGUI->MergePolyGroups, &LInfo->MergePolyGroups, TRUE);

				EXeStart;
				EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNAlignmentX, XeAlignBEGINNING);
				EXeSetArg(XeNMarginSizeX, 2);EXeSetArg(XeNMarginSizeY, 3);
				EXeSetArg(XeNResizableY, FALSE);
				LGUI->StatusLabelW = XeWidget_ManagedNew("Status", XeLabelWidget_class, LTopMatrixW,  Args, ArgCnt);
				_StatusLabelUpdate(LInfo, LGUI);
			}

			if(LGUI)
			{
				const E3dpPrefs*  LPrefs = &E3dp_Prefs;
				const uint32_t    LNodeNameRGBAp8Color   = LPrefs->NodeNameRGBAp8Color;
				const uint32_t    LObjectNameRGBAp8Color = LPrefs->ObjectNameRGBAp8Color;
				const char*       LNoNameStr = "<no name>";

				EIndex  LI = 0;for(;LI < 2;LI++)
				{
					XeString  LXeStr = XeString_NewVa(TRUE,
					 XeFONT, XeFontDEFAULT,  XeCOLOR_RGBA, LNodeNameRGBAp8Color, XeTEXT, LInfo->Models[LI] ? (LInfo->Models[LI]->Name ? LInfo->Models[LI]->Name : LNoNameStr) : "-",  XeTEXT, " / ",
					 XeFONT, XeFontDEFAULT,  XeCOLOR_RGBA, LObjectNameRGBAp8Color,  XeTEXT, LInfo->Geometries[LI] ? (LInfo->Geometries[LI]->Name ? LInfo->Geometries[LI]->Name : LNoNameStr) : "-",
					 0);
					Xe_SetValuesVa(LGUI->MeshNameLabelWs[LI],  XeNLabelString, LXeStr,  NULL);
					XeString_Free(LXeStr);
				}
			}
		break;
	}
}


//========================================
// Menu callback
//========================================
static void _MCB(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	if(((XePushButtonCallbackStruct*)PCallData)->Reason == XeCR_ACTIVATE_OPTIONS) { _PopupDialog();return; }

	E3dScene*  LScene = E3d_Scene;
	const int  LOperation = (int)(EPointerInt)PClientData;

	EListAI_s(, E3dObjectPtr, LObjects);EIndex  LObjects_Count = E3dScene_GetObjects(LScene, E3dItemMESH, E3dSELECTED, FALSE, &LObjects);if(LObjects_Count == 2)
	{
		{
			E3dModel*  LModelA = E3dObject_GetSelectingModel(LObjects.Items[0], FALSE);if(LModelA == NULL) goto End;
			E3dModel*  LModelB = E3dObject_GetSelectingModel(LObjects.Items[1], FALSE);if(LModelB == NULL) goto End;

			_E3dTmp  LTmp = { .Operation= LOperation };
			_Create(LModelA, (E3dGeometry*)LObjects.Items[0],  (E3dGeometry*)LObjects.Items[1], LModelB,  &LTmp);
		}

End:
		EList_FreeA_s(LObjects);
	}
	else if(LObjects_Count == 1)
	{
		E3dObject*  LObjectA = LObjects.Items[0];E3dModel*  LModelA = E3dObject_GetSelectingModel(LObjectA, FALSE);
		if(LModelA == NULL) { EList_FreeA_s(LObjects);return; }

		_E3dTmp*  LTmp = &_Tmp;
		E_AssignToMember(LModelA, LTmp,Models[0]);
		E_AssignToMember(LObjectA, LTmp,Geometries[0]);
		LTmp->InputID = 1;
		LTmp->Operation = LOperation;
		_RequestInputPick(E3dMesh_class, NULL, LTmp);
	}
	else if(LObjects_Count == 0)
	{
		E3dp_PrintMessage(ELogWARNING, 5000, "Select at least the first Mesh");
	}
}


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

	{
		E3dObjectComponentClass  LObjectsClass=
		{
			.Name=        "Boolean",
			.StructSize=  sizeof(E3dInfo),
			.Resources=   EListS_s(_Resources),

			.Init=        _Init,
			.Copy=        _Copy,
			.Free=        _Free,
			.Update1=      _Update1,
			.GUI=         _GUI
		};
		_ObjectComponent_class = E3dObjectComponent_ClassRegister(&LObjectsClass);
	}

	if(_ObjectComponent_class)
	{
		_MenuButtons[_MenuButtons_Count++] = XePushButton_Add(Xe_App, "Menu->Polygons", "Boolean : subtract", '\0', NULL, NULL, TRUE, NULL, _MCB, (EPointer)E3dSUBTRACTION);
		_MenuButtons[_MenuButtons_Count++] = XePushButton_Add(Xe_App, "Menu->Polygons", "Boolean : intersection", '\0', NULL, NULL, TRUE, NULL, _MCB, (EPointer)E3dINTERSECTION);
		_MenuButtons[_MenuButtons_Count++] = XePushButton_Add(Xe_App, "Menu->Polygons", "Boolean : union", '\0', NULL, NULL, TRUE, NULL, _MCB, (EPointer)E3dUNION);
	}

	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;
	}

	if(_ObjectComponent_class) { E3dObjectComponent_ClassDeactivate(_ObjectComponent_class);_ObjectComponent_class = NULL; }

	return(E_SUCCESS);
}
