private static void GenerateConvexPolygonTriIndices( LevelGeometryTesselator.Polygon poly, int[] indexMap, GroupListBuilder builder, GroupMaterial material )
        {
            GroupBuilder group = builder.GetGroup( material );
            ICollection< int > indices = group.Indices;

            int baseIndex = indexMap[ poly.Edges[0].StartIndex ];
            for ( int index = 1; index < poly.Edges.Length - 1; ++index )
            {
                indices.Add( baseIndex );
                indices.Add( indexMap[ poly.Edges[ index ].StartIndex ] );
                indices.Add( indexMap[ poly.Edges[ index ].EndIndex ] );
            }
        }
        /// <summary>
        /// Builds runtime environment graphics from level geometry
        /// </summary>
        /// <param name="envGraphics">Environment graphics to build</param>
        /// <param name="geometry">Level geometry</param>
        /// <returns>Returns a new <see cref="IEnvironmentGraphics"/> object</returns>
        public IEnvironmentGraphics Build( IEnvironmentGraphics envGraphics, LevelGeometry geometry )
        {
            //	Create environment graphics

            Csg2.Node root = Csg2.Build( geometry.ObstaclePolygons );
            List< LevelGeometryTesselator.Polygon > floorPolys = new List< LevelGeometryTesselator.Polygon >( );
            List< LevelGeometryTesselator.Polygon > obstaclePolys = new List< LevelGeometryTesselator.Polygon >( );

            LevelGeometryTesselator tess = new LevelGeometryTesselator( );
            LevelGeometryTesselator.Polygon poly = tess.CreateBoundingPolygon( -100, -100, 100, 100 );

            LevelGeometryTesselator.AddPolygonDelegate addFloorPoly =
                delegate( LevelGeometryTesselator.Polygon floorPoly, Csg2.Node node )
                {
                    floorPolys.Add( floorPoly );
                };

            LevelGeometryTesselator.AddPolygonDelegate addObstaclePoly =
                delegate( LevelGeometryTesselator.Polygon obstaclePoly, Csg2.Node node )
                {
                    obstaclePolys.Add( obstaclePoly );
                };

            tess.BuildConvexRegions( root, poly, addFloorPoly, addObstaclePoly, true );

            GroupListBuilder groupsBuilder = new GroupListBuilder( );

            Point2[] flatPoints = tess.Points.ToArray( );

            //    //	Get the texture source for the wall
            //    ITexture2d textureSource = node.Edge.WallData.Texture;
            //    ITechnique techniqueSource = node.Edge.WallData.Technique;

            //    GroupMaterial material = new GroupMaterial( techniqueSource, new ITexture2d[] { textureSource } );
            //    GroupBuilder group = builder.GetGroup( material );
            StaticGeometryData defaultFloor = StaticGeometryData.CreateDefaultFloorData( );
            StaticGeometryData defaultWall = StaticGeometryData.CreateDefaultWallData( );
            GroupMaterial defaultFloorMaterial = new GroupMaterial
                (
                    defaultFloor.Technique, new ITexture2d[] { defaultFloor.Texture }
                );
            GroupMaterial defaultWallMaterial = new GroupMaterial
                (
                    defaultWall.Technique, new ITexture2d[] { defaultWall.Texture }
                );
            GroupMaterial defaultObstacleMaterial = new GroupMaterial
                (
                    defaultFloor.Technique, new ITexture2d[] { defaultFloor.Texture }
                );

            int[] floorIndexMap = new int[ flatPoints.Length ];
            int[] roofIndexMap = new int[ flatPoints.Length ];

            for ( int index = 0; index < flatPoints.Length; ++index )
            {
                floorIndexMap[ index ] = -1;
                roofIndexMap[ index ] = -1;
            }

            List< Vertex > vertices = new List< Vertex >( flatPoints.Length * 2 );

            foreach ( LevelGeometryTesselator.Polygon floorPoly in floorPolys )
            {
                for ( int edgeIndex = 0; edgeIndex < floorPoly.Edges.Length; ++edgeIndex )
                {
                    int pIndex = floorPoly.Edges[ edgeIndex ].StartIndex;
                    if ( floorIndexMap[ pIndex ] == -1 )
                    {
                        Point2 srcPt = flatPoints[ pIndex ];
                        floorIndexMap[ pIndex ] = AddVertex( vertices, FloorVertex( srcPt.X, 0, srcPt.Y ) );
                    }
                }
                GenerateConvexPolygonTriIndices( floorPoly, floorIndexMap, groupsBuilder, defaultFloorMaterial );
            }

            foreach ( LevelGeometryTesselator.Polygon obstaclePoly in obstaclePolys )
            {
                //	Duplicate floor points at obstacle height, add wall polys
                for ( int edgeIndex = 0; edgeIndex < obstaclePoly.Edges.Length; ++edgeIndex )
                {
                    int pIndex = obstaclePoly.Edges[ edgeIndex ].StartIndex;
                    if ( roofIndexMap[ pIndex ] == -1 )
                    {
                        Point2 srcPt = flatPoints[ pIndex ];
                        roofIndexMap[ pIndex ] = AddVertex( vertices, FloorVertex( srcPt.X, 6, srcPt.Y ) );
                    }
                }
                //	Generate wall polys
                for ( int edgeIndex = 0; edgeIndex < obstaclePoly.Edges.Length; ++edgeIndex )
                {
                    int pIndex = obstaclePoly.Edges[ edgeIndex ].StartIndex;
                    int nextPIndex = obstaclePoly.Edges[ edgeIndex ].EndIndex;

                    if ( ( floorIndexMap[ pIndex ] == -1 ) || ( floorIndexMap[ nextPIndex ] == -1 ) )
                    {
                        //	No equivalent floor vertices - ignore
                        continue;
                    }
                    //if ( floorIndexMap[ pIndex ] == -1 )
                    //{
                    //    floorIndexMap[ pIndex ] = AddVertex( vertices, FloorVertex( flatPoints[ pIndex ].X, 0, flatPoints[ pIndex ].Y ) );
                    //}
                    //if ( floorIndexMap[ nextPIndex ] == -1 )
                    //{
                    //    floorIndexMap[ nextPIndex ] = AddVertex( vertices, FloorVertex( flatPoints[ nextPIndex ].X, 0, flatPoints[ nextPIndex ].Y ) );
                    //}

                    Point3 floor0Pt = vertices[ floorIndexMap[ pIndex ] ].m_Point;
                    Point3 floor1Pt = vertices[ floorIndexMap[ nextPIndex ] ].m_Point;

                    Point3 roof0Pt	= vertices[ roofIndexMap[ pIndex ] ].m_Point;
                    Point3 roof1Pt	= vertices[ roofIndexMap[ nextPIndex ] ].m_Point;

                    Vector3 dir = Vector3.Cross( floor1Pt - floor0Pt, floor0Pt - roof0Pt ).MakeNormal( );

                    //	TODO: AP: Better texture coordinate generation
                    float texWidth = floor0Pt.DistanceTo( floor1Pt ) / 5.0f;
                    float texHeight = floor0Pt.DistanceTo( roof0Pt ) / 5.0f;

                    int floorP0 = AddVertex( vertices, new Vertex( floor0Pt, dir, new Point2( 0, 0 ) ) );
                    int floorP1 = AddVertex( vertices, new Vertex( floor1Pt, dir, new Point2( texWidth, 0 ) ) );
                    int roofP0 = AddVertex( vertices, new Vertex( roof0Pt, dir, new Point2( 0, texHeight ) ) );
                    int roofP1 = AddVertex( vertices, new Vertex( roof1Pt, dir, new Point2( texWidth, texHeight ) ) );

                    GroupBuilder group = groupsBuilder.GetGroup( defaultWallMaterial );
                    group.Indices.Add( floorP0 );
                    group.Indices.Add( floorP1 );
                    group.Indices.Add( roofP0 );

                    group.Indices.Add( floorP1 );
                    group.Indices.Add( roofP1 );
                    group.Indices.Add( roofP0 );
                }

                GenerateConvexPolygonTriIndices( obstaclePoly, roofIndexMap, groupsBuilder, defaultObstacleMaterial );
            }

            VertexBufferData buffer = VertexBufferData.FromVertexCollection( vertices );
            EnvironmentGraphicsData.GridCell cell = new EnvironmentGraphicsData.GridCell( buffer );

            foreach ( GroupBuilder group in groupsBuilder.Groups )
            {
                cell.Groups.Add( group.Create( ) );
            }

            EnvironmentGraphicsData data = new EnvironmentGraphicsData( 1, 1 );
            data[ 0, 0 ] = cell;

            envGraphics.Build( data );

            return envGraphics;

            /*
            EnvironmentGraphicsData envGraphicsData = new EnvironmentGraphicsData( 1, 1 );

            EnvironmentGraphicsData.GridCell levelCell = new EnvironmentGraphicsData.GridCell( );

            EnvironmentGraphicsData.CellGeometryGroup[] groups = CreateGroups( geometry.Csg.Root );
            levelCell.Groups.AddRange( groups );

            envGraphicsData[ 0, 0 ] = levelCell;

            envGraphics.Build( envGraphicsData );

            return envGraphics;

            /*
            //	Determine x/z bounds of the geometry
            Rectangle levelBounds = GetLevelBounds( geometry.Csg.Root );

            int xDivisions = ( int )( levelBounds.Width / m_ChopSize ) + 1;
            int yDivisions = ( int )( levelBounds.Height / m_ChopSize ) + 1;

            float y = levelBounds.Y;

            TriBuilder builder = new TriBuilder( );

            for ( int yDiv = 0; yDiv < yDivisions; ++yDiv, y += m_ChopSize )
            {
                float x = levelBounds.X;
                for ( int xDiv = 0; xDiv < xDivisions; ++xDiv, x += m_ChopSize )
                {
                    ClippetyClip( geometry.Csg.Root, x, y, x + m_ChopSize, y + m_ChopSize, builder );
                }
            }

            return null;
            */
        }