(
	struct shapeExtensions
	(
		private getNumSplines,
		private getPolyLineLength,
		private getPolyLinePointsFlat,

		public fn getShapePoints shape spline = if isKindOf shape ::Shape do
		(
			local isNode = isValidNode shape
			local handle = getHandleByAnim shape
			local splineCount = getNumSplines handle currentTime.ticks isNode

			if splineCount >= spline then
			(
				local pointData = getPolyLinePointsFlat handle currentTime.ticks (spline - 1) isNode
				for p = 1 to pointData.count by 3 collect [pointData[p], pointData[p + 1], pointData[p + 2]]
			)
			else #()
		),

		on create do
		(
			local compilerParams = dotNetObject "System.CodeDom.Compiler.CompilerParameters" #(
				getDir #maxRoot + "Autodesk.Max.dll",
				getDir #maxRoot + "MaxPlusDotNet.dll",
				getDir #maxRoot + "\bin\assemblies\Autodesk.Max.Wrappers.dll")
			compilerParams.GenerateInMemory = true

			local compilerResults = (dotNetObject "Microsoft.CSharp.CSharpCodeProvider").CompileAssemblyFromSource compilerParams #(
				"using System;
				using Autodesk.Max;
				using Autodesk.Max.Wrappers;
				using System.Collections.Generic;
				using Constants = Autodesk.Max.MaxPlus.Constants;

				public class ShapeActions {
					public static readonly IGlobal Global = Autodesk.Max.GlobalInterface.Instance;

					public static readonly IClass_ID spline3DClassID = Global.Class_ID.Create((uint)BuiltInClassIDA.SPLINE3D_CLASS_ID, 0);
					public static readonly IClass_ID splineShapeClassID = Global.Class_ID.Create((uint)BuiltInClassIDA.SPLINESHAPE_CLASS_ID, 0);
					public static readonly IClass_ID linearShapeClassID = Global.Class_ID.Create((uint)BuiltInClassIDA.LINEARSHAPE_CLASS_ID, 0);

					public static ILinearShape GetLinearShape() {
						return (ILinearShape)Global.COREInterface14.CreateInstance(SClass_ID.Shape, linearShapeClassID);
					}

					public static ISplineShape GetSplineShape() {
						return (ISplineShape)Global.COREInterface14.CreateInstance(SClass_ID.Shape, splineShapeClassID);
					}
					
					public static IBezierShape GetBezierShapeFromObject(System.UIntPtr animHandle, int time) {
						IShapeObject obj = (IShapeObject)Global.Animatable.GetAnimByHandle(animHandle);
						return GetBezierShapeFromShape(obj, time);
					}

					public static IBezierShape GetBezierShapeFromNode(System.UIntPtr animHandle, int time) {
						IINode node = (IINode)Global.Animatable.GetAnimByHandle(animHandle);
						var obj = node.EvalWorldState(time, true).Obj;
						return GetBezierShapeFromShape(obj, time);
					}

					public static IBezierShape GetBezierShapeFromShape(Autodesk.Max.IObject shape, int time) {
						if (shape.CanConvertToType(spline3DClassID) > 0) {
							IShapeObject spObject = (IShapeObject)shape;
							IBezierShape bezShape = (GetSplineShape()).Shape;
							spObject.MakeBezier(time, bezShape);
							return bezShape;
						}
						else if (shape.CanConvertToType(splineShapeClassID) > 0) {
							ISplineShape spShape = (ISplineShape)shape.ConvertToType(time, splineShapeClassID);
							return (IBezierShape)spShape.Shape;
						}
						else return null;
					}

					public static IPolyShape GetPolyShape(System.UIntPtr animHandle, int time, bool isNode) {
						IBezierShape bezShape = isNode
							? GetBezierShapeFromNode(animHandle, time)
							: GetBezierShapeFromObject(animHandle, time);
						IPolyShape polyShape = (GetLinearShape()).Shape;
						bezShape.MakePolyShape(polyShape, Constants.PshapeBuiltinSteps, bezShape.Optimize);
						return polyShape;
					}

					public static int GetNumSplines(System.UIntPtr animHandle, int time, bool isNode) {
						IBezierShape bezShape = isNode
							? GetBezierShapeFromNode(animHandle, time)
							: GetBezierShapeFromObject(animHandle, time);
						return bezShape.SplineCount;
					}

					public static Single[] GetPolyLinePoints(System.UIntPtr animHandle, int time, int index, bool isNode) {
						IPolyLine pLine = (GetPolyShape(animHandle, time, isNode)).Lines[index];
						IList<Single> pointList = new List<Single>();
						Single[] points = new Single[3 * pLine.Pts.Count];

						foreach (IPolyPt pt in pLine.Pts) {
							pointList.Add(pt.P.X);
							pointList.Add(pt.P.Y);
							pointList.Add(pt.P.Z);
						}
						pointList.CopyTo(points, 0);

						return points;
					}
				}"
			)

			for err = 0 to compilerResults.errors.count - 1 do print (compilerResults.errors.item[err].ToString())

			local shapeActions = compilerResults.CompiledAssembly.CreateInstance "ShapeActions"
			getPolyLinePointsFlat = shapeActions.GetPolyLinePoints
			getNumSplines = shapeActions.GetNumSplines
		)
	)
	::shapeExtensions = shapeExtensions()
	OK
)

plugin simpleObject WeaveMesh 
	name:"Weave Mesh" 
	category:"Examples"
	classID:#(0x603e68e4, 0x434e6001)
--	author:"Vojtech Cada"
(
	fn filterShapes obj = isKindOf obj Shape
	fn isEven nr = bit.and 1L nr == 0	
	fn isOdd nr = not isEven nr

	mapped fn addPtsToBBox pt bBox =
		expandToInclude bBox pt

	fn getCirclePoints count radius =
	(
		local step = 360d0 / count

		for i = 1 to count collect
			x_axis * radius * sin (i * step) + y_axis * radius * cos (i * step)
	)

	fn buildQuadStrip length offset segCount profilePts tm flip &vertCount &vertList &faceList = 
	(
		local segLength = length / segCount
		local sideCount = profilePts.count
		local workingTM = copy tm

		for seg = 1 to segCount + 1 do
		(
			workingTM.pos = (seg - 1) * segLength * tm.row3 + (if isEven seg then tm.pos + tm.row1 * (flip *= -1) * offset else tm.pos)
			join vertList (for i = sideCount to 1 by - 1 collect profilePts[i] * workingTM)

			for pt = 1 to sideCount do
			(
				vertCount += 1

				if seg > 1 do if pt == 1 then
				(
					append faceList [vertCount, vertCount + sideCount - 1, vertCount - 1]
					append faceList [vertCount - 1, vertCount - sideCount, vertCount]
				)
				else
				(
					append faceList [vertCount, vertCount - 1, vertCount - sideCount - 1]
					append faceList [vertCount - sideCount - 1, vertCount - sideCount, vertCount]
				)
			)
		)
	)

	fn hasCustomProfile =
		this.useCustomShape and isValidNode this.profileShape and isKindOf this.profileShape Shape

	parameters main rollout:params
	(
		width type:#worldUnits ui:spnWidth default:1
		length type:#worldUnits ui:spnLength default:1
		horizontalCount type:#integer ui:spnHorizontalCount default:10
		verticalCount type:#integer ui:spnVerticalCount default:10

		height type:#worldUnits ui:spnDiameter default:1
		sideCount type:#integer ui:spnSideCount default:4

		useCustomShape type:#boolean default:off animatable:off ui:chxUseCustomShape
		profileShape type:#node ui:pbtPickProfile
	)

	rollout params "Weave Mesh Parameters"
	(
		group " Common "
		(
			spinner spnWidth "Width: " range:[0,1e9,1] type:#worldUnits
			spinner spnLength "Length: " range:[0,1e9,1] type:#worldUnits
			spinner spnHorizontalCount "Horiz. Count: " range:[1,1e9,1] type:#integer
			spinner spnVerticalCount "Vert. Count: " range:[1,1e9,1] type:#integer
		)

		group " Default Profile "
		(
			spinner spnSideCount "Sides: " range:[3,1e9,3] type:#integer enabled:(not useCustomShape)
			spinner spnDiameter "Diameter: " range:[0,1e9,1] type:#worldUnits enabled:(not useCustomShape)
		)

		group " Custom Profile "
		(
			checkBox chxUseCustomShape "Use Shape From Scene" offset:[5,0]
			pickButton pbtPickProfile "Pick" width:125 filter:filterShapes autoDisplay:on enabled:useCustomShape \

			on chxUseCustomShape changed state do
				#(spnSideCount, spnDiameter).enabled = not (pbtPickProfile.enabled = state)
		)
	)

	on buildMesh do
	(
		disableRefMsgs() 

		try
		(
			local vertCount = 0
			local vertList = #()
			local faceList = #()

			local horizontalStep = width / horizontalCount
			local horizontalPos = (horizontalStep - width) / 2 - horizontalStep
			local verticalStep = length / verticalCount
			local verticalPos = (verticalStep - length) / 2 - verticalStep

			local profilePts = if hasCustomProfile() and (local pts = shapeExtensions.getShapePoints profileShape 1).count > 2 then
			(
				local profileBounds = Box3()
				addPtsToBBox pts profileBounds
				height = 2 * abs((profileBounds.max - profileBounds.center).y)

				for pt in pts collect [pt.y - profileBounds.center.y, pt.x - profileBounds.center.x, 0]
			)
			else getCirclePoints sideCount (height / 2)

			for h = 1 to horizontalCount do
				buildQuadStrip length (height / 2) (2 * verticalCount) profilePts \
					(matrix3 z_axis x_axis y_axis [horizontalPos += horizontalStep, -length / 2, 0]) (if isEven h then 1 else -1) &vertCount &vertList &faceList

			for v = 1 to verticalCount do
				buildQuadStrip width (height / 2) (2 * horizontalCount) profilePts \
					(matrix3 z_axis -y_axis x_axis [-width / 2, verticalPos += verticalStep, 0]) (if isOdd v then 1 else -1) &vertCount &vertList &faceList

			setMesh mesh vertices:vertList faces:faceList
			for face in mesh.faces do setEdgeVis mesh face.index 3 off
		)
		catch ()

		enableRefMsgs() 
	)

	tool create numPoints:3
	(
		on mousePoint click do
			if click == 1 do nodeTM.translation = gridPoint

		on mouseMove click do case click of
		(
			2: (width = 2 * abs gridDist.x; length = 2 * abs gridDist.y)
			3: height = gridDist.z
		)
	)
)