# EPSexportPlugin
# Initial code generated by XSI SDK Wizard
# Executed Sun Oct 15 11:28:22 UTC+0100 2006 by kim
# 
# 
# ToDo:
"""

	line colours from wireframe/diffuse
	Output current viewport
	cap ends of thick lines???
	xsi import compatable mode

"""
import win32com.client
from win32com.client import constants

import math
import datetime
import os

null = None
false = 0
true = 1

siWarning = 4

def XSILoadPlugin( in_reg ): #{{{
	in_reg.Author = "kim"
	in_reg.Name = "EPSexportPlugin"
	in_reg.Email = ""
	in_reg.URL = ""
	in_reg.Major = 1
	in_reg.Minor = 0

	in_reg.RegisterProperty("EPSexport")
	in_reg.RegisterMenu(constants.siMenuMainFileExportID,"EPSexport_Menu",false,false)

	# register the export command
	# 	KA_EpsExport( Application.Selection, OutputFile, CullBackFaces, doInnerLines, doBorderLines, InnerLineThickness, BorderLineThickness, AutoDiscontinuity, Xres )
	in_reg.RegisterCommand("KA_EpsExport","KA_EpsExport")

	#RegistrationInsertionPoint - do not remove this line

	return true
#}}}
def XSIUnloadPlugin( in_reg ): #{{{
	strPluginName = in_reg.Name
	#Application.LogMessage(str(strPluginName) + str(" has been unloaded."))
	return true
#}}}
def EPSexport_Define( ctxt ): #{{{


	oCustomProperty = ctxt.Source
	oCustomProperty.AddParameter2("CullBackFaces",constants.siBool,false,null,null,null,null,0,constants.siPersistable)
	oCustomProperty.AddParameter2("InnerEdges",constants.siBool,true,null,null,null,null,0,constants.siPersistable)
	oCustomProperty.AddParameter2("BorderEdges",constants.siBool,true,null,null,null,null,0,constants.siPersistable)
	oCustomProperty.AddParameter2("AutoDiscontinuity",constants.siFloat,10,0,90,0,90,0,constants.siPersistable)

	oCustomProperty.AddParameter2("LineThickness",constants.siFloat,1,0,500,0,50,0,constants.siPersistable)
	oCustomProperty.AddParameter2("BorderThickness",constants.siFloat,2,0,500,0,50,0,constants.siPersistable)
	oCustomProperty.AddParameter2("Filename",constants.siString,"",null,null,null,null,0,constants.siPersistable)


	oXSICams = Application.ActiveProject.ActiveScene.Root.FindChildren( "*", constants.siCameraPrimType )
	sDefaultCam = oXSICams(0).Name;

	oCustomProperty.AddParameter2("Camera",constants.siString,sDefaultCam,null,null,null,null,0,constants.siPersistable)

	oCustomProperty.AddParameter2("Scale",constants.siFloat,500,0,1500,0,500,0,constants.siPersistable)
	return true
#}}}
def EPSexport_DefineLayout( ctxt ): #{{{

	oLayout = ctxt.Source
	oLayout.Clear()
	
	oXSICams = Application.ActiveProject.ActiveScene.Root.FindChildren( "*", constants.siCameraPrimType )

	# add the camera dropdown
	
	oLayout.AddGroup( "" );

	oLayout.AddStaticText( "EPS Export by Kim Aldis. " );
	oLayout.AddStaticText( "www.kim-aldis.co.uk" );

	oLayout.EndGroup();

	aList = []

	for i in range( oXSICams.count ):
		aList.append( oXSICams(i).Name )
		aList.append( oXSICams(i).Name )

	oLayout.AddEnumControl( "Camera", aList )
	
	oLayout.AddItem("CullBackFaces")
	oLayout.AddItem("InnerEdges")
	oLayout.AddItem("BorderEdges")
	oLayout.AddItem("LineThickness")
	oLayout.AddItem("BorderThickness")
	oLayout.AddItem("AutoDiscontinuity", "AutoDiscontinuity (degrees)" )
	oLayout.AddItem("Scale", "X Res")

	oLayout.AddRow()
	oLayout.AddItem("Filename")
	oLayout.AddButton( "Browser", "..." )
	oLayout.EndRow()

	oLayout.AddButton( "Export" )
	return true
#}}}
def EPSexport_OnInit( ): #{{{
	#Application.LogMessage("OnInit called")

	oXSICams = Application.ActiveProject.ActiveScene.Root.FindChildren( "*", constants.siCameraPrimType )

	aList = []

	for i in range( oXSICams.count ):
		aList.append( oXSICams(i).Name )
		aList.append( oXSICams(i).Name )
	

	oItem = PPG.Inspected(0).PPGLayout.Item( "Camera" )

	oItem.UIItems = aList

	PPG.Refresh()

#}}}
def EPSexport_Menu_Init( ctxt ): #{{{
	oMenu = ctxt.Source
	oMenu.AddCallbackItem("  EPS Export by Kim Aldis","OnEPSexportMenuClicked")
	return true
#}}}

# Menu click, installs ppg in scene root and presents it to the user
# Actual Export is initiated by the user.
def OnEPSexportMenuClicked( ctxt ): #{{{

	oProp = Application.ActiveSceneRoot.properties( "EPSexport" )

	if ( not oProp ):

		oProp = Application.ActiveSceneRoot.AddProperty("EPSexport")


	Application.InspectObj( oProp )
	return 1
#}}}

# File Browser '...' button callback
# Pops up a browser and populates the filename field of the ppg
#
def EPSexport_Browser_OnClicked(): #{{{

	oPset = PPG.Inspected(0);

	oUIToolkit = win32com.client.Dispatch( 'XSI.UIToolKit' )

	oFileBrowser = oUIToolkit.FileBrowser
	oFileBrowser.DialogTitle = "Select a file"
	#oFileBrowser.InitialDirectory = initialDir
	oFileBrowser.Filter = "EPS Files (*.eps)|*.eps||"
	oFileBrowser.ShowSave()

	if ( str( oFileBrowser.FilePathName ) != "" ) :

		oPset.Filename = oFileBrowser.FilePathName;

		
#}}}

def EPSexport_Export_OnClicked(): #{{{

	oPset = PPG.Inspected(0);

	# Grab ppg parameters
	#
	OutputFile = oPset.Filename.value
	CullBackFaces = oPset.CullBackFaces.value 				
	doInnerLines = oPset.InnerEdges.value
	doBorderLines = oPset.BorderEdges.value
	InnerLineThickness = oPset.LineThickness.value
	BorderLineThickness = oPset.BorderThickness.value
	AutoDiscontinuity = oPset.AutoDiscontinuity.value
	Xres = oPset.Scale.value


	# and call the command to export
	#
	KA_EpsExport( Application.Selection, OutputFile, CullBackFaces, doInnerLines, doBorderLines, InnerLineThickness, BorderLineThickness, AutoDiscontinuity, Xres )
	

#}}}

def KA_EpsExport_Init( ctxt ): #{{{
	oCmd = ctxt.Source
	oCmd.Description = ""
	oCmd.ReturnValue = true

	oArgs = oCmd.Arguments
	oArgs.Add("ItemList",constants.siArgumentInput)
	oArgs.Add("OutputFile",constants.siArgumentInput)
	oArgs.Add("CullBackFaces",constants.siArgumentInput)
	oArgs.Add("doInnerLines",constants.siArgumentInput)
	oArgs.Add("doBorderLines",constants.siArgumentInput)
	oArgs.Add("InnerLineThickness",constants.siArgumentInput)
	oArgs.Add("BorderLineThickness",constants.siArgumentInput)
	oArgs.Add("AutoDiscontinuity",constants.siArgumentInput)
	oArgs.Add("Xres",constants.siArgumentInput)
	return true
#}}}

##########################################################
# Command callbacks
##########################################################

def KA_EpsExport_Execute( ItemList,OutputFile, CullBackFaces, doInnerLines, doBorderLines, InnerLineThickness, BorderLineThickness, AutoDiscontinuity, Xres ): #{{{

	return KA_EpsExport( ItemList,OutputFile, CullBackFaces, doInnerLines, doBorderLines, InnerLineThickness, BorderLineThickness, AutoDiscontinuity, Xres )

#}}}
def BuildEPSCode( EdgeList, tThickness, tScale ) : #{{{

	s = "%d setlinewidth\n" % (tThickness)
	for  oEdge in EdgeList :
		vP1 = oEdge[0]
		vP2 = oEdge[1]

		xm = (vP1.X + 1.0) * tScale /2.0
		ym = (vP1.Y + 1.0) * tScale /2.0
		xt = (vP2.X + 1.0) * tScale /2.0
		yt = (vP2.Y + 1.0) * tScale /2.0
		s = s + "%d %d M\n%d %d L\n" % (xm+0.5,ym+0.5,xt+0.5,yt+0.5)

	s = s + "stroke\n"
	return s
#}}}

def KA_EpsExport(  ItemList,OutputFile, CullBackFaces, doInnerLines, doBorderLines, InnerLineThickness, BorderLineThickness, AutoDiscontinuity, Xres ) : #{{{


	oXSICam = Application.ActiveProject.ActiveScene.Root.FindChildren( "*", constants.siCameraPrimType )(0)
	oCam = KA_Camera(  oXSICam )

	oCam.DiscontinuityThreshold = math.radians( AutoDiscontinuity )
	oCam.CullBackFaces = CullBackFaces;

	oCam.Inner = doInnerLines
	oCam.Border = doBorderLines

	s = ""
	

	for  oObj in ItemList  :
		Application.LogMessage( "EPSExport: Transforming : " + oObj.Name )

		if ( oObj.Type == "uvspace" ) :

			s = EPSHead( Xres/2.0, 1.0 )

			Application.LogMessage( "UV Stamp" );
			oUVSpace = oObj

			oParent = oUVSpace.Parent3DObject

			#the array of uv coordinates, nUVs * 3 (UVW)
			aUVW = oUVSpace.Elements

			oPolys = oParent.ActivePrimitive.Geometry.Polygons


			s += "%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n"
			s += "%% UVSpace : " + oObj.Name + "\n"

			s = s + "%d setlinewidth\n" % (InnerLineThickness)

			s = s + "1 1 0 setrgbcolor\n"


			for i in range( oPolys.count ) :
				oNodePoints = oPolys(i).Points
				nNodes = oNodePoints.count
				oNodes = oPolys(i).Nodes

				for j in range( oNodePoints.count ) :
					if ( j == oNodePoints.count -1 ) :
						jNext = 0
					else :
						jNext = j+1
	
					#iPointIndex = oNodePoints(j).index;
					iUVINdex1 = oNodes(j).index;
					iUVINdex2 = oNodes(jNext).index;

					U1 = aUVW(iUVINdex1)[0] * Xres;
					V1 = aUVW(iUVINdex1)[1] * Xres;

					U2 = aUVW(iUVINdex2)[0] * Xres;
					V2 = aUVW(iUVINdex2)[1] * Xres;
				#	Application.LogMessage( "UV %f, %f" % (U,V) )
					s = s + "%d %d M\n%d %d L\n" % (U1 + 0.5,V1 + 0.5,U2 + 0.5,V2 + 0.5)
				
				s = s + "stroke\n"

			
		elif ( oObj.Type == "polymsh" ) :

			s = EPSHead( Xres/2.0, oXSICam.aspect.value )

			s += "%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n"
			s += "%% Object : " + oObj.Name + "\n"
			s += "%% Inner Geometry\n"

			iWireCol = Application.GetValue( oObj.name + ".display.wirecol" );
			r = ((iWireCol >> 1) & 0x7) / 7.0
  			g = ((iWireCol >> 4) & 0x7) / 7.0
  			b= ((iWireCol >> 7) & 0x7) / 7.0
			
			s = s + "%f %f %f setrgbcolor\n" % (r,g,b)


			InnerSave = oCam.Inner
			BorderSave = oCam.Border

			if ( oCam.Inner ) :
				oCam.Border = false

				Application.LogMessage( oObj.ActivePrimitive.Geometry )
				EdgeList = oCam.WorldToScreen( oObj.ActivePrimitive.Geometry.Edges )
				s += BuildEPSCode( EdgeList, InnerLineThickness, Xres )

				oCam.Border = BorderSave
				DrawList( EdgeList, true, 1,0,0 );

			if ( oCam.Border ) :
				oCam.Inner = false

				s += "%% Border\n"
				EdgeList2 = oCam.WorldToScreen( oObj.ActivePrimitive.Geometry.Edges )
				s += BuildEPSCode( EdgeList2, BorderLineThickness, Xres )

				DrawList( EdgeList2, false, 0,1,0 );

				oCam.Inner = InnerSave
		else :
			Application.LogMessage( "KA_EPSexport: wrong type for EPS Export: " + oObj.Name + "( type=" + oObj.Type + ")", siWarning  )


	# ToDo: error check this!
	fp = open( OutputFile, 'w' )
	fp.write( s )
	fp.close()

	Box( 1, 1.0/oXSICam.aspect.value );

	Application.LogMessage( "EPSExport: Written to %s " %( OutputFile) )
#}}}
# Build an eps header
# return as string
def EPSHead( tScale, tAspectRatio ): #{{{


	s = """%!PS-Adobe-2.0
%%Title: EPSexportFromXSI
%%Creator: EPSExporter By Kim Aldis
%% Exported from XSI Version: """ 

	s = s + Application.Version()

	s = s + """
%% User: """

	if ( XSIUtils.IsLinuxOS() ) :
		s = s + os.environ.get( "USER" )
	else :
		s = s + os.environ.get( "USERNAME" )

	s = s + """
%%CreationDate: """

	s = s + datetime.datetime.now().strftime("%A (%a) %d/%m/%Y") 

	s = s + """
%%DocumentFonts: (atend)
"""

	s = s + '%%' + "BoundingBox: -%d -%d %d %d\n" % ( tScale, tScale/tAspectRatio, tScale, tScale/tAspectRatio )

	s = s + """
%%Orientation: Landscape
%%Pages: (atend)
%%EndComments
%# 
/M {moveto} bind def
/L {lineto} bind def
%# 
"""
	s = s + "-%d -%d translate \n" % (tScale, tScale )

	s = s + """
0 setgray
newpath
"""

	return s
#}}}

######################## Class Definitions
class KA_Camera: #{{{
	
	import math
	def __init__( self, oCam ):
		self.XSI_Camera = oCam
		self.DiscontinuityThreshold = 0.0 # angle between poly normals >= this value = will draw
		self.CullBackFaces = false
		self.Border = true
		self.Inner = true
	
	
		 
	####################################################################
	# Main world to screen transformation method
	# takes an XSI Edge Collection, transforms and clips it
	# into Screen space and returns a list of edges in internal format
	# Internal format edgelist is a tuple of tuples of XSI Vectors, one 
	# vector for each end of the edge
	#
	def WorldToScreen( self, oEdgeList ): #{{{
	
		oCam = self.XSI_Camera
		
		# get the inverse camera matrix
		#
		CamP = oCam.Kinematics.Global.transform.Matrix4
		CamP.InvertInPlace()

		ReturnList = []

		for oEdge in oEdgeList :

			pp = self.TransformEdge( oEdge )

			if ( len(pp) > 0 ) : # if it's zero length it's clipped out or invisible
				ReturnList.append( pp )

		return ReturnList

	#}}}		
	####################################################################
	# Clip an edge to the view frustrum
	# p1 and p2 : end point vectors in Screen space.
	#
	def ClipEdgeToViewFrustrum( self, p1,p2 ): #{{{

		oCam = self.XSI_Camera
		tAspectRatio = oCam.aspect.value 
		yScreenMax = 1.0/tAspectRatio

		tNear = oCam.near.value
		tFar = oCam.far.value

		#  easy ones out of the way first
		# If these conditions are satisfied then the entire edge lies right outside
		# the view frustrum.
		if (p1.X < -1.0  and p2.X < -1.0 ) :
			return None
		if (p1.X > 1.0  and p2.X > 1.0 ) :
			return None
		if (p1.Y < -yScreenMax  and p2.Y < -yScreenMax ) :
			return None
		if (p1.Y > yScreenMax  and p2.Y > yScreenMax ) :
			return None
		if (p1.Z > tFar  and p2.Z > tFar ) :
			return None
		if (p1.Z < tNear  and p2.Z < tNear ) :
			return None

		# Clip to left
		#
		iClipped = false
		[x,y,z, isClipped] = self.ClipEdgeToPlane( -1.0, p1.X, p1.Y, p1.Z, p2.X, p2.Y, p2.Z )
		if ( isClipped ) :
			iClipped = true
			if ( p1.X < -1.0 ) :
				p1.X = x
				p1.Y = y
				p1.Z = z
			else :
				p2.X = x
				p2.Y = y
				p2.Z = z
	
		# to right
		[x,y,z,isClipped] = self.ClipEdgeToPlane( 1.0, p1.X, p1.Y,p1.Z,  p2.X, p2.Y, p2.Z )
		if ( isClipped ) :
			iClipped = true
			if ( p1.X > 1.0 ) :
				p1.X = x
				p1.Y = y
				p1.Z = z
			else :
				p2.X = x
				p2.Y = y
				p2.Z = z

		#to top
		[x,y,z,isClipped] = self.ClipEdgeToPlane( yScreenMax, p1.Y, p1.X,p1.Z, p2.Y, p2.X,p2.Z )
		if ( isClipped ) :
			iClipped = true
			if ( p1.Y > yScreenMax ) :
				p1.Y = x
				p1.X = y
				p1.Z = z
			else :
				p2.Y = x
				p2.X = y
	
		#to Bottom
		[x,y,z,isClipped] = self.ClipEdgeToPlane( -yScreenMax, p1.Y,p1.X,p1.Z, p2.Y,p2.X,p2.Z )
		if ( isClipped ) :
			iClipped = true
			if ( p1.Y < -yScreenMax ) :
				p1.Y = x
				p1.X = y
				p1.Z = z
			else :
				p2.Y = x
				p2.X = y
				p2.Z = z

		# far clip
		[x,y,z,isClipped] = self.ClipEdgeToPlane( tFar, p1.Z, p1.Y,p1.X, p2.Z, p2.Y,p2.X )
		if ( isClipped ) :
			iClipped = true
			if ( p1.Z > tFar ) :
				p1.Z = x
				p1.Y = y
				p1.X = z
			else :
				p2.Z = x
				p2.Y = y
				p2.X = z

		# near clip
		[x,y,z,isClipped] = self.ClipEdgeToPlane( tNear, p1.Z, p1.Y,p1.X, p2.Z, p2.Y,p2.X )
		if ( isClipped ) :
			iClipped = true
			if ( p1.Z < tNear ) :
				p1.Z = x
				p1.Y = y
				p1.X = z
			else :
				p2.Z = x
				p2.Y = y
				p2.X = z

		return [p1,p2, iClipped]
#}}}
	####################################################################
	# transform a point into Screen space
	# multiply by Inverse of camera matrix 
	# then do the perspective divide
	#
	def WorldPoint2Screen( self, p ): #{{{
	
		oCam = self.XSI_Camera
		
		CamP = oCam.Kinematics.Global.transform.Matrix4
		CamP.InvertInPlace()
		
		p.MulByMatrix4InPlace( CamP )
		p.Z = -p.Z
		p = self.PerspectiveDivide( p )
	
		return p
#}}}
	####################################################################
	# Camer to Screen space transform
	#
	def PerspectiveDivide( self, p ): #{{{

		oCam = self.XSI_Camera

		tAspectRatio = oCam.aspect.value
		fov = math.radians(oCam.fov.value)

		iVertical = 0
		iHorizontal = 1

		# if it's a vertical fov, translate into horizontal
		#	
		if ( oCam.fovtype.value == iVertical ):
			fov	/= tAspectRatio;

		x = p.X
		y = p.Y
		z = p.Z

		# fov is full fov angle
		#
		t = z*math.tan( fov/2.0 )
		x /= t
		y /= t

		p = XSIMath.CreateVector3( x,y,z );

		return p
#}}}
	#######################################################################
	# Decide whether this edge needs to be drawn and if so
	# if it's a border edge once in screen space.#
	# Checks include:
	#	Back Facing
	#  Sillhouetted edge
	#  Auto Discontinuity threshold
	#
	def TransformEdge( self, oEdge ) : #{{{

		# we may not need to draw this edge
		doDraw = false


		oNeighbourPolys = oEdge.NeighborPolygons(1)
		if ( self.DiscontinuityThreshold > 0.0 or self.CullBackFaces ) :


			oP1 = KA_Poly( oNeighbourPolys[0] ) # transform into world space here!!
			oP1.NormalToWorld()

			P1Visible = self.PolyIsVisible( oP1 )
			P2Visible = 0;

			if ( oNeighbourPolys.Count == 2 ):
				oP2 = KA_Poly( oNeighbourPolys[1] )
				oP2.NormalToWorld()
				P2Visible = self.PolyIsVisible( oP2 )

			# one visible, the other not; it's a boundary edge, we draw it no matter what
			if ( P1Visible + P2Visible == 1 ) :
				if ( self.Border ) :
					doDraw = true
				else :
					doDraw = false

			# both visible; check against discontinuity threshold
			if ( P1Visible + P2Visible == 2 ) :
				if ( self.Inner ) :
					Theta = self.AngleBetweenPolys( oP1, oP2 )
					if ( Theta >= self.DiscontinuityThreshold ) :
						doDraw = true
					else:
						doDraw = false
				else :
					doDraw = false


			#both invisible; draw only of CullBackFaces == true
			if ( P1Visible + P2Visible == 0 ) :
				if ( self.Inner ) :
					if ( self.CullBackFaces ) :
						doDraw = false
					else :
						Theta = self.AngleBetweenPolys( oP1, oP2 )
						if ( Theta >= self.DiscontinuityThreshold ) :
							doDraw = true
						else:
							doDraw = false

				else  :
					doDraw = false

		if doDraw :

			oObj = oEdge.Parent.Parent.Parent
			p1 = XSIMath.CreateVector3()
			p2 = XSIMath.CreateVector3()
			p1.MulByMatrix4( oEdge.Points[0].position, oObj.Kinematics.Global.Transform.Matrix4 );
			p2.MulByMatrix4( oEdge.Points[1].position, oObj.Kinematics.Global.Transform.Matrix4 );

			p1 = self.WorldPoint2Screen( p1 )
			p2 = self.WorldPoint2Screen( p2 )
	
			pp =  self.ClipEdgeToViewFrustrum( p1, p2 )

			if ( pp ) :
				return pp
			else : 
				return []

		return []

	#}}}

	######################################################################
	# clip an edge to a plane
	# Plane here is defined by t, where t is distance from origin along U axis
	# We can clip against the plane in X, Y or Z by choosing the order of X,Y,Z in our
	# calling function
	#
	def ClipEdgeToPlane( self, t, U1, V1, W1, U2, V2, W2 ) : #{{{


		# quick check to see if our edge crosses our clip line
		# if it doesn't clip then we can assume we're completely
		# inside the line because we've already done our outside check
		#
		if ( math.fabs(t-U1) + math.fabs(U2-t) > math.fabs(U2-U1) ) :
			return [None,None,None,false]

		x = t
		y = V1 + (V2 -V1)/(U2-U1) * (t-U1)
		z = W1 + (W2 -W1)/(U2-U1) * (t-U1)

		return [x,y,z, true]

	#}}}
	######################################################################
	# is the polygon visible to the camera
	#
	def PolyIsVisible( self, oP ): #{{{
		
		vCamVec = XSIMath.CreateVector3()

		vCam = XSIMath.CreateVector3();

		self.XSI_Camera.Kinematics.Global.Transform.GetTranslation( vCam )
		vCamVec.Sub(  vCam, oP.Centre, );
		vCamVec.NormalizeInPlace()


		vp = XSIMath.CreateVector3()

		cosTheta = vCamVec.Dot( oP.Normal );

		#Application.LogMessage( "Theta: " + str( cosTheta ) )

		if ( cosTheta <= 0.0 ) :
			return 0


		return 1

		#}}}
		#####################################################################
		# get the angle between two polygons for the Auto Discontinuity check
		#
	def AngleBetweenPolys( self, oP1, oP2 ): #{{{

		cosTheta = oP1.Normal.Dot( oP2.Normal )

		# fix Python math domain error. wuh??
		#
		if ( cosTheta >= 1.0 ) :
			cosTheta = 1.0

		return math.acos( cosTheta - 0.0000 )
	#}}}
	#}}}


# Polygon utilities
#
class KA_Poly: #{{{

	def __init__( self, oPoly ):

		self.XSI_Poly = oPoly
		self.Normal = XSIMath.CreateVector3( 0.0,0.0,0.0 )
		self.Centre = XSIMath.CreateVector3( 0.0,0.0,0.0 )

		self.GetNormal()		# calculate the face normal
		self.GetCentre()		# calculate the face centre


	# Calculate the best normal to a polygon
	# Average of cross products at each node
	# 
	def GetNormal( self ):

		oPoly = self.XSI_Poly

		vp = XSIMath.CreateVector3( 0.0,0.0,0.0 )

		vNext = XSIMath.CreateVector3( )
		vPrevious = XSIMath.CreateVector3( )

		self.Normal.Set( 0.0,0.0,0.0 )

		for iThis in range( oPoly.Points.count ):

			
			if ( iThis == oPoly.Points.count-1 ):
				iNext = 0
			else :
				iNext = iThis+1

			if ( iThis == 0 ) :
				iPrevious = oPoly.Points.count-1
			else :
				iPrevious = iThis - 1


			vNext.Sub( oPoly.Points(iNext).position, oPoly.Points(iThis).position )
			vPrevious.Sub( oPoly.Points(iPrevious).position, oPoly.Points(iThis).position )

			vNext.NormalizeInPlace();
			vPrevious.NormalizeInPlace()

			vp.Cross( vNext, vPrevious )

			self.Normal.AddInPlace( vp )

		self.Normal.NormalizeInPlace()


	# Get the centre of a polygon
	# Average of all node coordinates
	def GetCentre( self ):

		oPoly = self.XSI_Poly

		self.Centre.Set( 0.0,0.0,0.0)

		for oPoint in oPoly.Points:
			self.Centre.AddInPlace( oPoint.position )


		self.Centre.ScaleInPlace( 1.0 / ( oPoly.points.count ) )


	# transform the polygon normal to world space
	# using it's owner object transfrom
	def NormalToWorld( self ):

		oTrans = self.XSI_Poly.Parent.Parent.Parent.Kinematics.Global.Transform
		self.Normal = XSIMath.MapObjectOrientationToWorldSpace(  oTrans, self.Normal )
		

#}}}	


def pVec( s, v ) :
	
	Application.LogMessage( "%s : [%f, %f, %f]" % ( s, v.X,v.Y,v.Z ) );

def Box( x, y ):

	oSeq = Application.NewSequencer();
	#oSeq.Flush()

	p1 = XSIMath.CreateVector3( x,y,0 )
	p2 = XSIMath.CreateVector3( x,-y,0 )
	p3 = XSIMath.CreateVector3( -x,-y,0 )
	p4 = XSIMath.CreateVector3( -x,y,0 )

	p1.ScaleInPlace( 10 )
	p2.ScaleInPlace( 10 )
	p3.ScaleInPlace( 10 )
	p4.ScaleInPlace( 10 )

	oSeq.DrawLine( p1,p2, 0,0,1 )
	oSeq.DrawLine( p2,p3, 0,0,1 )
	oSeq.DrawLine( p3,p4, 0,0,1 )
	oSeq.DrawLine( p4,p1, 0,0,1 )



def DrawList( EdgeList, Flush, r,g,b ) :

	oSeq = Application.NewSequencer();
	if ( Flush ) :
		oSeq.Flush()

	for oEdge in EdgeList :
		p1 = XSIMath.CreateVector3( oEdge[0].X, oEdge[0].Y, 0 )
		p2 = XSIMath.CreateVector3( oEdge[1].X, oEdge[1].Y, 0 )

		p1.ScaleInPlace( 10 )
		p2.ScaleInPlace( 10 )
		oSeq.DrawLine( p1,p2, r,g,b )
		#pVec( "P1", p1 )
		#pVec( "P2", p2 )
