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;
        }
        // LWOB
        public static Surface ReadSurface5(ChunkReader reader, LightwaveObject obj)
        {
            var surf = new Surface();
            surf.name = reader.ReadString();

            Texture tex = null;
            LightwavePlugin shdr = null;
            float[] v = new float[3];
            uint flags = 0;

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

                using (var subChunkReader = reader.GetSubChunk(sz))
             				{
                    switch (id)
                    {
                        case SurfaceParameter.ID_COLR:
                        {
                            surf.color.Red = subChunkReader.ReadUInt8() / 255.0f;
                            surf.color.Green = subChunkReader.ReadUInt8() / 255.0f;
                            surf.color.Blue = subChunkReader.ReadUInt8() / 255.0f;
                            break;
                        }

                        case SurfaceParameter.ID_FLAG:
                        {
                            flags = subChunkReader.ReadUInt16();
                            if ((flags & 4) != 0) surf.smooth = 1.56207f;
                            if ((flags & 8) != 0) surf.color_hilite.value = 1.0f;
                            if ((flags & 16) != 0) surf.color_filter.value = 1.0f;
                            if ((flags & 128) != 0) surf.dif_sharp.value = 0.5f;
                            if ((flags & 256) != 0) surf.sideflags = 3;
                            if ((flags & 512) != 0) surf.add_trans.value = 1.0f;
                            break;
                        }

                        case SurfaceParameter.ID_LUMI:
                        {
                            surf.luminosity.Value = subChunkReader.ReadSInt16() / 256.0f;
                            break;
                        }

                        case SurfaceParameter.ID_VLUM:
                        {
                            surf.luminosity.Value = subChunkReader.ReadSingle();
                            break;
                        }

                        case SurfaceParameter.ID_DIFF:
                        {
                            surf.diffuse.Value = subChunkReader.ReadSInt16() / 256.0f;
                            break;
                        }

                        case SurfaceParameter.ID_VDIF:
                        {
                            surf.diffuse.Value = subChunkReader.ReadSingle();
                            break;
                        }

                        case SurfaceParameter.ID_SPEC:
                        {
                            surf.specularity.Value = subChunkReader.ReadSInt16() / 256.0f;
                            break;
                        }

                        case SurfaceParameter.ID_VSPC:
                        {
                            surf.specularity.Value = subChunkReader.ReadSingle();
                            break;
                        }

                        case SurfaceParameter.ID_GLOS:
                        {
                            surf.glossiness.Value = (float)Math.Log(subChunkReader.ReadUInt16()) / 20.7944f;
                            break;
                        }

                        case SurfaceParameter.ID_SMAN:
                        {
                            surf.smooth = subChunkReader.ReadSingle();
                            break;
                        }

                        case SurfaceParameter.ID_REFL:
                        {
                            surf.reflection.values.Value = subChunkReader.ReadSInt16() / 256.0f;
                            break;
                        }

                        case SurfaceParameter.ID_RFLT:
                        {
                            surf.reflection.options = subChunkReader.ReadUInt16();
                            break;
                        }

                        case SurfaceParameter.ID_RIMG:
                        {
                            var s = subChunkReader.ReadString();
                            surf.reflection.clip_index = AddClip(s, obj.Clips);
                            surf.reflection.options = 3;
                            break;
                        }

                        case SurfaceParameter.ID_RSAN:
                        {
                            surf.reflection.seam_angle = subChunkReader.ReadSingle();
                            break;
                        }

                        case SurfaceParameter.ID_TRAN:
                        {
                            surf.transparency.values.Value = subChunkReader.ReadSInt16() / 256.0f;
                            break;
                        }

                        case SurfaceParameter.ID_RIND:
                        {
                            surf.eta.Value = subChunkReader.ReadSingle();
                            break;
                        }

                        case SurfaceParameter.ID_BTEX:
                        {
                            var s = subChunkReader.ReadString(sz);//.getbytes((uint)sz);
                            tex = ParseTexture(s);
                            surf.bump.textures.Add(tex);
                            break;
                        }

                        case SurfaceParameter.ID_CTEX:
                        {
                            var s = subChunkReader.ReadString(sz);//.getbytes((uint)sz);
                            tex = ParseTexture(s);
                            surf.color.textures.Add(tex);
                            break;
                        }

                        case SurfaceParameter.ID_DTEX:
                        {
                            var s = subChunkReader.ReadString(sz);//.getbytes((uint)sz);
                            tex = ParseTexture(s);
                            surf.diffuse.textures.Add(tex);
                            break;
                        }

                        case SurfaceParameter.ID_LTEX:
                        {
                            var s = subChunkReader.ReadString(sz);//.getbytes((uint)sz);
                            tex = ParseTexture(s);
                            surf.luminosity.textures.Add(tex);
                            break;
                        }

                        case SurfaceParameter.ID_RTEX:
                        {
                            var s = subChunkReader.ReadString(sz);//.getbytes((uint)sz);
                            tex = ParseTexture(s);
                            surf.reflection.values.textures.Add(tex);
                            break;
                        }

                        case SurfaceParameter.ID_STEX:
                        {
                            var s = subChunkReader.ReadString(sz);//.getbytes((uint)sz);
                            tex = ParseTexture(s);
                            surf.specularity.textures.Add(tex);
                            break;
                        }

                        case SurfaceParameter.ID_TTEX:
                        {
                            var s = subChunkReader.ReadString(sz);//.getbytes((uint)sz);
                            tex = ParseTexture(s);
                            surf.transparency.values.textures.Add(tex);
                            break;
                        }

                        case SurfaceParameter.ID_TFLG:
                        {
                            if (tex == null)
                                return null;
                            flags = subChunkReader.ReadUInt16();

                            int i = 0;
                            if ((flags & 1) != 0) i = 0;
                            if ((flags & 2) != 0) i = 1;
                            if ((flags & 4) != 0) i = 2;
                            tex.axis = (ushort)i;
                            if (tex.Type == TextureType.ID_IMAP)
                                tex.imap.axis = i;
                            else
                                tex.proc.axis = i;

                            if ((flags & 8) != 0) tex.tmap.CoordinateSystem = 1;
                            if ((flags & 16) != 0) tex.negative = 1;
                            if ((flags & 32) != 0) tex.imap.pblend = 1;
                            if ((flags & 64) != 0)
                            {
                                tex.imap.aa_strength = 1.0f;
                                tex.imap.aas_flags = 1;
                            }
                            break;
                        }

                        case SurfaceParameter.ID_TSIZ:
                        {
                            tex.tmap.Size.values[0] = subChunkReader.ReadSingle();
                            tex.tmap.Size.values[1] = subChunkReader.ReadSingle();
                            tex.tmap.Size.values[2] = subChunkReader.ReadSingle();
                            break;
                        }

                        case SurfaceParameter.ID_TCTR:
                        {
                            tex.tmap.Center.values[0] = subChunkReader.ReadSingle();
                            tex.tmap.Center.values[1] = subChunkReader.ReadSingle();
                            tex.tmap.Center.values[2] = subChunkReader.ReadSingle();
                            break;
                        }

                        case SurfaceParameter.ID_TFAL:
                        {
                            tex.tmap.FallOff.values[0] = subChunkReader.ReadSingle();
                            tex.tmap.FallOff.values[1] = subChunkReader.ReadSingle();
                            tex.tmap.FallOff.values[2] = subChunkReader.ReadSingle();
                            break;
                        }

                        case SurfaceParameter.ID_TVEL:
                        {
                            for (var i = 0; i < 3; i++)
                                v[i] = subChunkReader.ReadSingle();
                            tex.tmap.Center.envelope_index = AddTextureVelocity(tex.tmap.Center.values, v, obj.Envelopes);
                            break;
                        }

                        case SurfaceParameter.ID_TCLR:
                        {
                            if (tex.Type == TextureType.ID_PROC)
                            {
                                tex.proc.value_0 = subChunkReader.ReadUInt8() / 255.0f;
                                tex.proc.value_1 = subChunkReader.ReadUInt8() / 255.0f;
                                tex.proc.value_2 = subChunkReader.ReadUInt8() / 255.0f;
                            }
                            break;
                        }

                        case SurfaceParameter.ID_TVAL:
                        {
                            tex.proc.value_0 = subChunkReader.ReadSInt16() / 256.0f;
                            break;
                        }

                        case SurfaceParameter.ID_TAMP:
                        {
                            if (tex.Type == TextureType.ID_IMAP)
                                tex.imap.amplitude.value = subChunkReader.ReadSingle();
                            break;
                        }

                        case SurfaceParameter.ID_TIMG:
                        {
                            var s = subChunkReader.ReadString();
                            tex.imap.clip_index = AddClip(s, obj.Clips);
                            break;
                        }

                        case SurfaceParameter.ID_TAAS:
                        {
                            tex.imap.aa_strength = subChunkReader.ReadSingle();
                            tex.imap.aas_flags = 1;
                            break;
                        }

                        case SurfaceParameter.ID_TREF:
                        {
                            tex.tmap.ReferenceObject = subChunkReader.ReadString((uint)sz);//.getbytes((uint)sz);
                            break;
                        }

                        case SurfaceParameter.ID_TOPC:
                        {
                            tex.opacity.value = subChunkReader.ReadSingle();
                            break;
                        }

                        case SurfaceParameter.ID_TFP0:
                        {
                            if (tex.Type == TextureType.ID_IMAP)
                                tex.imap.wrapw.value = subChunkReader.ReadSingle();
                            break;
                        }

                        case SurfaceParameter.ID_TFP1:
                        {
                            if (tex.Type == TextureType.ID_IMAP)
                                tex.imap.wraph.value = subChunkReader.ReadSingle();
                            break;
                        }

                        case SurfaceParameter.ID_SHDR:
                        {
                            shdr = new LightwavePlugin();
                            if (shdr == null)
                                return null;
                            shdr.name = subChunkReader.ReadString((uint)sz);
                            surf.shader.Add(shdr);
                            break;
                        }

                        case SurfaceParameter.ID_SDAT:
                        {
                            if (shdr == null)
                                return null;
                            shdr.data = subChunkReader.ReadBytes(sz);
                            break;
                        }

                        default:
                            Console.WriteLine("Unknown surface parameter type " + reader.GetIDString((uint)id));
                            break;
                    }
                }
            }
            return surf;
        }
        public static LightwavePlugin ReadShader(ChunkReader reader)
        {
            var shader = new LightwavePlugin();

            var hsz		= reader.ReadUInt16(); // Q: example code does this, but can't find this in documentation?
            shader.ord	= reader.ReadString();

            using (var headerReader = reader.GetSubChunk(hsz))
            {
                while (headerReader.BytesLeft > 0)
                {
                    var id = headerReader.ReadID<PluginType>();
                    var sz = headerReader.ReadUInt16();
                    sz += (ushort)(sz & 1);
                    hsz -= sz;
                    if (id == PluginType.ID_ENAB)
                    {
                        shader.flags = headerReader.ReadUInt16();
                        break;
                    }
                }
            }

            while (reader.BytesLeft > 0)
            {
                var id = reader.ReadID<PluginType>();
                var sz = reader.ReadUInt16();
                sz += (ushort)(sz & 1);

                using (var subChunkReader = reader.GetSubChunk(sz))
                {
                    switch (id)
                    {
                        case PluginType.ID_FUNC:
                        {
                            shader.name = subChunkReader.ReadString();
                            shader.data = subChunkReader.ReadBytes((uint)subChunkReader.BytesLeft);
                            break;
                        }

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

            return shader;
        }
        public static Surface ReadSurface(ChunkReader reader)
        {
            // allocate the Surface structure
            var surf = new Surface();

            // names
            surf.name		= reader.ReadString();
            surf.srcname	= reader.ReadString();

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

                using (var subChunkReader = reader.GetSubChunk(sz))
                {
                    switch (id)
                    {
                        // Vertex Color Map - VCOL { intensity[FP4], envelope[VX], vmap-type[ID4], name[S0] }
                        //		The vertex color map subchunk identifies an RGB or RGBA VMAP that will be used to color the surface
                        case SurfaceParameter.ID_VCOL:
                        {
                            var intensity		= subChunkReader.ReadSingle();
                            var envelope_index	= subChunkReader.ReadVariableLengthIndex();
                            var vmap_type		= subChunkReader.ReadUInt32();
                            var name			= subChunkReader.ReadString();
                            break;
                        }

                        case SurfaceParameter.ID_CMNT: // Not mentioned in LWO documentation, maybe means 'comment'?
                        {
                            break;
                        }

                        case SurfaceParameter.ID_VERS: // Not mentioned in LWO documentation, maybe means 'version'?
                        {
                            break;
                        }

                        case SurfaceParameter.ID_NODS: // Not mentioned in LWO documentation, maybe means 'nodes'?
                        {
                            break;
                        }

                        case SurfaceParameter.ID_COLR:
                        {
                            surf.color.Red							= subChunkReader.ReadSingle();
                            surf.color.Green						= subChunkReader.ReadSingle();
                            surf.color.Blue							= subChunkReader.ReadSingle();
                            surf.color.envelope_index				= subChunkReader.ReadVariableLengthIndex();
                            break;
                        }

                        case SurfaceParameter.ID_LUMI:
                            surf.luminosity.Value					= subChunkReader.ReadSingle();
                            surf.luminosity.envelope_index			= subChunkReader.ReadVariableLengthIndex();
                            break;

                        case SurfaceParameter.ID_DIFF:
                            surf.diffuse.Value						= subChunkReader.ReadSingle();
                            surf.diffuse.envelope_index				= subChunkReader.ReadVariableLengthIndex();
                            break;

                        case SurfaceParameter.ID_SPEC:
                            surf.specularity.Value					= subChunkReader.ReadSingle();
                            surf.specularity.envelope_index			= subChunkReader.ReadVariableLengthIndex();
                            break;

                        case SurfaceParameter.ID_GLOS:
                            surf.glossiness.Value					= subChunkReader.ReadSingle();
                            surf.glossiness.envelope_index			= subChunkReader.ReadVariableLengthIndex();
                            break;

                        case SurfaceParameter.ID_REFL:
                            surf.reflection.values.Value			= subChunkReader.ReadSingle();
                            surf.reflection.values.envelope_index	= subChunkReader.ReadVariableLengthIndex();
                            break;

                        case SurfaceParameter.ID_RFOP:
                            surf.reflection.options					= subChunkReader.ReadUInt16();
                            break;

                        case SurfaceParameter.ID_RIMG:
                            surf.reflection.clip_index				= subChunkReader.ReadVariableLengthIndex();
                            break;

                        // Reflection Blurring - RBLR { blur-percentage[FP4], envelope[VX] }
                        //		The amount of blurring of reflections. The default is zero.
                        case SurfaceParameter.ID_RBLR:
                            break;

                        case SurfaceParameter.ID_RSAN:
                            surf.reflection.seam_angle				= subChunkReader.ReadSingle();
                            break;

                        case SurfaceParameter.ID_TRAN:
                            surf.transparency.values.Value			= subChunkReader.ReadSingle();
                            surf.transparency.values.envelope_index	= subChunkReader.ReadVariableLengthIndex();
                            break;

                        case SurfaceParameter.ID_TROP:
                            surf.transparency.options				= subChunkReader.ReadUInt16();
                            break;

                        case SurfaceParameter.ID_TIMG:
                            surf.transparency.clip_index			= subChunkReader.ReadVariableLengthIndex();
                            break;

                        case SurfaceParameter.ID_RIND:
                            surf.eta.Value							= subChunkReader.ReadSingle();
                            surf.eta.envelope_index					= subChunkReader.ReadVariableLengthIndex();
                            break;

                        case SurfaceParameter.ID_TRNL:
                            surf.translucency.Value					= subChunkReader.ReadSingle();
                            surf.translucency.envelope_index		= subChunkReader.ReadVariableLengthIndex();
                            break;

                        case SurfaceParameter.ID_BUMP:
                            surf.bump.Value							= subChunkReader.ReadSingle();
                            surf.bump.envelope_index				= subChunkReader.ReadVariableLengthIndex();
                            break;

                        case SurfaceParameter.ID_SMAN:
                            surf.smooth								= subChunkReader.ReadSingle();
                            break;

                        case SurfaceParameter.ID_SIDE:
                            surf.sideflags							= subChunkReader.ReadUInt16();
                            break;

                        case SurfaceParameter.ID_CLRH:
                            surf.color_hilite.value					= subChunkReader.ReadSingle();
                            surf.color_hilite.envelope_index		= subChunkReader.ReadVariableLengthIndex();
                            break;

                        case SurfaceParameter.ID_CLRF:
                            surf.color_filter.value					= subChunkReader.ReadSingle();
                            surf.color_filter.envelope_index		= subChunkReader.ReadVariableLengthIndex();
                            break;

                        case SurfaceParameter.ID_ADTR:
                            surf.add_trans.value					= subChunkReader.ReadSingle();
                            surf.add_trans.envelope_index			= subChunkReader.ReadVariableLengthIndex();
                            break;

                        case SurfaceParameter.ID_SHRP:
                            surf.dif_sharp.value					= subChunkReader.ReadSingle();
                            surf.dif_sharp.envelope_index			= subChunkReader.ReadVariableLengthIndex();
                            break;

                        case SurfaceParameter.ID_GVAL:
                            surf.glow.value							= subChunkReader.ReadSingle();
                            surf.glow.envelope_index				= subChunkReader.ReadVariableLengthIndex();
                            break;

                        // Render Outlines - LINE { flags[U2], ( size[F4], size-envelope[VX], ( color[COL12], color-envelope[VX] )? )? }
                        case SurfaceParameter.ID_LINE:
                            surf.line.enabled = 1;
                            if (sz >= 2) surf.line.flags				= subChunkReader.ReadUInt16();
                            if (sz >= 6) surf.line.size.value			= subChunkReader.ReadSingle();
                            if (sz >= 8) surf.line.size.envelope_index	= subChunkReader.ReadVariableLengthIndex();
                            break;

                        case SurfaceParameter.ID_ALPH:
                            surf.alpha_mode = subChunkReader.ReadUInt16();
                            surf.alpha		= subChunkReader.ReadSingle();
                            break;

                        case SurfaceParameter.ID_AVAL:
                            surf.alpha		= subChunkReader.ReadSingle();
                            break;

                        case SurfaceParameter.ID_BLOK:
                        {
                            var type = subChunkReader.ReadID<TextureType>();
                            switch (type)
                            {
                                case TextureType.ID_IMAP:
                                case TextureType.ID_PROC:
                                case TextureType.ID_GRAD:
                                {
                                    using (var blockReader = subChunkReader.GetSubChunk((uint)subChunkReader.BytesLeft))
                                    {
                                        var tex = Texture.ReadTexture(blockReader, type);
                                        switch ( tex.Channel )
                                        {
                                            case TextureChannel.ID_COLR: surf.color					.textures.Add(tex); break;
                                            case TextureChannel.ID_LUMI: surf.luminosity			.textures.Add(tex); break;
                                            case TextureChannel.ID_DIFF: surf.diffuse				.textures.Add(tex); break;
                                            case TextureChannel.ID_SPEC: surf.specularity			.textures.Add(tex); break;
                                            case TextureChannel.ID_GLOS: surf.glossiness			.textures.Add(tex); break;
                                            case TextureChannel.ID_REFL: surf.reflection   .values	.textures.Add(tex); break;
                                            case TextureChannel.ID_TRAN: surf.transparency .values	.textures.Add(tex); break;
                                            case TextureChannel.ID_RIND: surf.eta					.textures.Add(tex); break;
                                            case TextureChannel.ID_TRNL: surf.translucency			.textures.Add(tex); break;
                                            case TextureChannel.ID_BUMP: surf.bump					.textures.Add(tex); break;
                                            default:
                                                throw new Exception("Unknown texture channel"); // TODO: create proper exception calass for this
                                        }
                                    }
                                    break;
                                }
                                case TextureType.ID_SHDR:
                                {
                                    using (var blockReader = subChunkReader.GetSubChunk((uint)subChunkReader.BytesLeft))
                                    {
                                        surf.shader.Add(
                                                LightwavePlugin.ReadShader(blockReader)
                                            );
                                    }
                                    break;
                                }
                                default:
                                    Console.WriteLine("Unknown blok type " + reader.GetIDString((uint)type));
                                    break;
                            }
                            break;
                        }

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

            return surf;
        }
 static IEnumerable<string> ReadTags(ChunkReader reader)
 {
     while (reader.BytesLeft > 0)
         yield return reader.ReadString();
 }