public static TextureMap ReadTextureMap(ChunkReader reader)
        {
            var tmap = new TextureMap();
            while (reader.BytesLeft > 0)
            {
                var id = reader.ReadID<TextureCoordinates>();
                var sz = reader.ReadUInt16();
                sz += (ushort)(sz & 1);

                using (var subChunkReader = reader.GetSubChunk(sz))
                {
                    switch (id)
                    {
                        case TextureCoordinates.ID_SIZE:
                            tmap.Size.values[0] = subChunkReader.ReadSingle();
                            tmap.Size.values[1] = subChunkReader.ReadSingle();
                            tmap.Size.values[2] = subChunkReader.ReadSingle();
                            tmap.Size.envelope_index = subChunkReader.ReadVariableLengthIndex();
                            break;

                        case TextureCoordinates.ID_CNTR:
                            tmap.Size.values[0] = subChunkReader.ReadSingle();
                            tmap.Size.values[1] = subChunkReader.ReadSingle();
                            tmap.Size.values[2] = subChunkReader.ReadSingle();
                            tmap.Center.envelope_index = subChunkReader.ReadVariableLengthIndex();
                            break;

                        case TextureCoordinates.ID_ROTA:
                            tmap.Size.values[0] = subChunkReader.ReadSingle();
                            tmap.Size.values[1] = subChunkReader.ReadSingle();
                            tmap.Size.values[2] = subChunkReader.ReadSingle();
                            tmap.Rotate.envelope_index = subChunkReader.ReadVariableLengthIndex();
                            break;

                        case TextureCoordinates.ID_FALL:
                            tmap.FallType = subChunkReader.ReadUInt16();
                            tmap.Size.values[0] = subChunkReader.ReadSingle();
                            tmap.Size.values[1] = subChunkReader.ReadSingle();
                            tmap.Size.values[2] = subChunkReader.ReadSingle();
                            tmap.FallOff.envelope_index = subChunkReader.ReadVariableLengthIndex();
                            break;

                        case TextureCoordinates.ID_OREF:
                            tmap.ReferenceObject = subChunkReader.ReadString();
                            break;

                        case TextureCoordinates.ID_CSYS:
                            tmap.CoordinateSystem = subChunkReader.ReadUInt16();
                            break;

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

            return tmap;
        }
        bool ReadImageMap(ChunkReader reader)
        {
            while (reader.BytesLeft > 0)
            {
                // get the next subchunk header
                var id = reader.ReadID<ImageMapType>();
                var sz = reader.ReadUInt16();
                sz += (ushort)(sz & 1);

                using (var subChunkReader = reader.GetSubChunk(sz))
                {
                    switch (id)
                    {
                        case ImageMapType.ID_TMAP:
                        {
                            tmap = TextureMap.ReadTextureMap(subChunkReader);
                            break;
                        }

                        case ImageMapType.ID_PROJ: imap.projection		= (ProjectionType)subChunkReader.ReadUInt16(); break;
                        case ImageMapType.ID_VMAP: imap.vertex_map_name = subChunkReader.ReadString(); break;
                        case ImageMapType.ID_AXIS: imap.axis			= subChunkReader.ReadUInt16(); break;
                        case ImageMapType.ID_IMAG: imap.clip_index		= subChunkReader.ReadVariableLengthIndex(); break;
                        case ImageMapType.ID_PIXB: imap.pblend			= subChunkReader.ReadUInt16(); break;

                        case ImageMapType.ID_WRAP:
                            imap.wrapw_type = (WrapType)subChunkReader.ReadUInt16();
                            imap.wraph_type = (WrapType)subChunkReader.ReadUInt16();
                            break;

                        case ImageMapType.ID_WRPW:
                            imap.wrapw.value = subChunkReader.ReadSingle();
                            imap.wrapw.envelope_index = subChunkReader.ReadVariableLengthIndex();
                            break;

                        case ImageMapType.ID_WRPH:
                            imap.wraph.value = subChunkReader.ReadSingle();
                            imap.wraph.envelope_index = subChunkReader.ReadVariableLengthIndex();
                            break;

                        case ImageMapType.ID_AAST:
                            imap.aas_flags = subChunkReader.ReadUInt16();
                            imap.aa_strength = subChunkReader.ReadSingle();
                            break;

                        case ImageMapType.ID_STCK:
                            imap.stck.value = subChunkReader.ReadSingle();
                            imap.stck.envelope_index = subChunkReader.ReadVariableLengthIndex();
                            break;

                        case ImageMapType.ID_TAMP:
                            imap.amplitude.value = subChunkReader.ReadSingle();
                            imap.amplitude.envelope_index = subChunkReader.ReadVariableLengthIndex();
                            break;

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

            return true;
        }
        bool ReadProcedural(ChunkReader reader)
        {
            while (reader.BytesLeft > 0)
            {
                var id = reader.ReadID<ProceduralType>();
                var sz = reader.ReadUInt16();
                sz += (ushort)(sz & 1);
                using (var subChunkReader = reader.GetSubChunk(sz))
                {
                    switch (id)
                    {
                        case ProceduralType.ID_TMAP:
                        {
                            tmap = TextureMap.ReadTextureMap(subChunkReader);
                            break;
                        }

                        case ProceduralType.ID_AXIS:
                        {
                            proc.axis = subChunkReader.ReadUInt16();
                            break;
                        }

                        case ProceduralType.ID_VALU:
                        {
                            proc.value_0 = subChunkReader.ReadSingle();
                            if (sz >= 8) proc.value_1 = subChunkReader.ReadSingle();
                            if (sz >= 12) proc.value_2 = subChunkReader.ReadSingle();
                            break;
                        }

                        case ProceduralType.ID_FUNC:
                        {
                            proc.name	= subChunkReader.ReadString();
                            proc.data	= subChunkReader.ReadBytes((uint)subChunkReader.BytesLeft);
                            break;
                        }

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

            return true;
        }
        bool ReadGradient(ChunkReader reader)
        {
            while (reader.BytesLeft > 0)
            {
                var id = reader.ReadID<GradientType>();
                var sz = reader.ReadUInt16();
                sz += (ushort)(sz & 1);

                using (var subChunkReader = reader.GetSubChunk(sz))
                {
                    switch (id)
                    {
                        case GradientType.ID_TMAP:
                        {
                            tmap = TextureMap.ReadTextureMap(subChunkReader);
                            break;
                        }

                        case GradientType.ID_PNAM:
                        {
                            grad.paramname = subChunkReader.ReadString();
                            break;
                        }

                        case GradientType.ID_INAM:
                        {
                            grad.itemname = subChunkReader.ReadString();
                            break;
                        }

                        case GradientType.ID_GRST:
                        {
                            grad.start = subChunkReader.ReadSingle();
                            break;
                        }

                        case GradientType.ID_GREN:
                        {
                            grad.end = subChunkReader.ReadSingle();
                            break;
                        }

                        case GradientType.ID_GRPT:
                        {
                            grad.repeat = subChunkReader.ReadUInt16();
                            break;
                        }

                        case GradientType.ID_FKEY:
                        {
                            var keys = new List<GradientKey>();
                            while (subChunkReader.BytesLeft > 0)
                            {
                                var key = new GradientKey();
                                key.value = subChunkReader.ReadSingle();
                                key.rgba_0 = subChunkReader.ReadSingle();
                                key.rgba_1 = subChunkReader.ReadSingle();
                                key.rgba_2 = subChunkReader.ReadSingle();
                                key.rgba_3 = subChunkReader.ReadSingle();
                                keys.Add(key);
                            }
                            grad.Keys = keys.ToArray();
                            break;
                        }

                        case GradientType.ID_IKEY:
                        {
                            var ikeys = new List<ushort>();
                            while (subChunkReader.BytesLeft > 0)
                                ikeys.Add(subChunkReader.ReadUInt16());
                            grad.ikey = ikeys.ToArray();
                            break;
                        }

                        case GradientType.ID_GVER:	// unknown, not mentioned in lightwave sdk documentation
                            break;

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