public static Envelope ReadEnvelope(ChunkReader reader)
        {
            var env = new Envelope(); // allocate the Envelope structure
            env.Index = reader.ReadVariableLengthIndex(); // index

            EnvelopeKey lastKey = null;
            while (reader.BytesLeft > 0)
            {
                // process subchunks as they're encountered
                var id = reader.ReadID<EnvelopeType>();
                var sz = reader.ReadUInt16();
                sz += (ushort)(sz & 1);

                using (var subChunkReader = reader.GetSubChunk(sz))
                {
                    switch (id)
                    {
                        case EnvelopeType.ID_TYPE:
                        {
                            env.Type = subChunkReader.ReadUInt16();
                            break;
                        }

                        case EnvelopeType.ID_NAME:
                        {
                            env.Name = subChunkReader.ReadString();
                            break;
                        }

                        case EnvelopeType.ID_PRE:
                        {
                            env.PreBehavior = (EvalType)subChunkReader.ReadUInt16();
                            break;
                        }

                        case EnvelopeType.ID_POST:
                        {
                            env.PostBehavior = (EvalType)subChunkReader.ReadUInt16();
                            break;
                        }

                        case EnvelopeType.ID_KEY:
                        {
                            lastKey = new EnvelopeKey(
                                                subChunkReader.ReadSingle(),	// time
                                                subChunkReader.ReadSingle()		// value
                                                );
                            env.Keys.Add(lastKey);

                            //TODO: not sort all the time
                            env.Keys.Sort((Comparison<EnvelopeKey>)delegate(EnvelopeKey k1, EnvelopeKey k2)
                            {
                                return k1.Time > k2.Time ? 1 : k1.Time < k2.Time ? -1 : 0;
                            });
                            break;
                        }

                        case EnvelopeType.ID_SPAN:
                        {
                            if (lastKey == null) // We should've encountered an ID_KEY before an ID_SPAN
                                throw new Exception("Key not defined"); //TODO: make proper exception class

                            lastKey.Shape = subChunkReader.ReadID<KeyShape>();
                            switch (lastKey.Shape)
                            {
                                case KeyShape.ID_TCB:
                                {
                                    lastKey.Tension		= subChunkReader.ReadSingle();
                                    lastKey.Continuity	= subChunkReader.ReadSingle();
                                    lastKey.Bias		= subChunkReader.ReadSingle();
                                    break;
                                }

                                case KeyShape.ID_BEZI:
                                case KeyShape.ID_HERM:
                                case KeyShape.ID_BEZ2:
                                {
                                    Array.Clear(lastKey.param, 0, lastKey.param.Length);
                                    int i = 0;
                                    while (i < 4 && subChunkReader.BytesLeft > 0)
                                    {
                                        lastKey.param[i] = subChunkReader.ReadSingle();
                                        i++;
                                    }
                                    break;
                                }

                                case KeyShape.ID_LINE:
                                    break;

                                default:
                                    Console.WriteLine("Unknown envelope span shape type " + reader.GetIDString((uint)lastKey.Shape));
                                    break;
                            }
                            break;
                        }

                        case EnvelopeType.ID_CHAN:
                        {
                            var plug = new LightwavePlugin();
                            plug.name	= subChunkReader.ReadString();
                            plug.flags	= subChunkReader.ReadUInt16();
                            plug.data	= subChunkReader.ReadBytes((uint)reader.BytesLeft);
                            env.ChannelFilters.Add(plug);
                            break;
                        }

                        default:
                            Console.WriteLine("Unknown envelope type " + reader.GetIDString((uint)id));
                            break;
                    }
                }
            }

            return env;
        }
        static void ReadPolygonTags(ChunkReader reader, List<Polygon> polygons, uint polygon_offset, uint tag_offset)
        {
            var type = reader.ReadID<PolygonTags>();
            if (type != PolygonTags.ID_SURF &&
                type != PolygonTags.ID_PART &&
                type != PolygonTags.ID_SMGP)
            {
                if (type != PolygonTags.ID_COLR &&	// 'Sketch Color Name'?
                    type != PolygonTags.ID_LXGN)	// unknown
                    Console.WriteLine("Unknown polygon tag type " + reader.GetIDString((uint)type));
                return;
            }

            while (reader.BytesLeft > 0)
            {
                var polygonIndex	= (int)(reader.ReadVariableLengthIndex() + polygon_offset);
                var tagIndex		= (int)(reader.ReadVariableLengthIndex() + tag_offset);

                switch (type)
                {
                    case PolygonTags.ID_SURF: polygons[polygonIndex].surf_index = (uint)tagIndex; break;
                    case PolygonTags.ID_PART: polygons[polygonIndex].part_index = (uint)tagIndex; break;
                    case PolygonTags.ID_SMGP: polygons[polygonIndex].SmoothingGroup = (int)tagIndex; break;

                    default:
                        Console.WriteLine("Unknown polygon tag type " + reader.GetIDString((uint)type));
                        break;
                }
            }
        }
        public static VertexMap ReadVertexMap(ChunkReader reader, bool perpoly)
        {
            var type		= reader.ReadID<VertexMapType>();
            var dimensions	= reader.ReadUInt16();
            var name		= reader.ReadString();

            var vertex_map	= new VertexMap(type, name, perpoly);

            var vertex_indices	= new List<uint>();
            var polygon_indices	= new List<uint>();
            var values			= new List<float[]>();

            // fill in the vertex-map values
            while (reader.BytesLeft > 0)
            {
                vertex_indices.Add(reader.ReadVariableLengthIndex());
                if (perpoly)
                    polygon_indices.Add(reader.ReadVariableLengthIndex());

                var pointValues = new float[dimensions];
                for (var j = 0; j < dimensions; j++)
                    pointValues[j] = reader.ReadSingle();
                values.Add(pointValues);
            }
            vertex_map.Values = values.ToArray();
            vertex_map.vertex_index = vertex_indices.ToArray();
            if (perpoly)
                vertex_map.polygon_index = polygon_indices.ToArray();
            return vertex_map;
        }
        static IEnumerable<Polygon> ReadPolygons(ChunkReader reader, uint vertex_offset)
        {
            var type = reader.ReadID<PolygonType>();

            // fill in the new polygons
            while (reader.BytesLeft > 0)
            {
                var vertex_count = reader.ReadUInt16();
                var flags = vertex_count & 0xFC00;
                vertex_count &= 0x03FF;

                var newPolygon = new Polygon(type, flags);
                for (var j = 0; j < vertex_count; j++)
                    newPolygon.Vertices.Add(
                            new PolygonVertex(reader.ReadVariableLengthIndex() + vertex_offset)
                        );

                yield return newPolygon;
            }
        }