// Returns the contents of a LWOB
        static LightwaveObject ReadObject5(ChunkReader reader)
        {
            // allocate an object and a default layer
            var newObject		= new LightwaveObject();
            var currentLayer	= new Layer();
            newObject.Layers.Add(currentLayer);

            uint pointOffset	= 0;
            uint polygonOffset	= 0;
            uint tagOffset		= 0;

            // process chunks as they're encountered
            while (reader.BytesLeft > 0)
            {
                var id		= reader.ReadID<ChunkType>();
                var cksize	= reader.ReadUInt32();
                cksize += cksize & 1;

                using (var subchunkReader = reader.GetSubChunk(cksize))
                {
                    switch (id)
                    {
                        case ChunkType.ID_PNTS:
                        {
                            pointOffset = (uint)currentLayer.Points.Count;
                            currentLayer.Points.AddRange(
                                        ReadPoints(subchunkReader)							// throws exception on failure
                                    );
                            break;
                        }

                        case ChunkType.ID_POLS:
                        {
                            polygonOffset = (uint)currentLayer.Polygons.Count;
                            currentLayer.Polygons.AddRange(
                                        ReadPolygons5(subchunkReader, pointOffset)			// throws exception on failure
                                    );
                            break;
                        }

                        case ChunkType.ID_SRFS:
                        {
                            tagOffset = (uint)newObject.Tags.Count;
                            newObject.Tags.AddRange(
                                        LightwaveObject.ReadTags(subchunkReader)			// throws exception on failure
                                    );
                            break;
                        }

                        case ChunkType.ID_SURF:
                        {
                            newObject.Surfaces.Add(
                                        Surface.ReadSurface5(subchunkReader, newObject)		// throws exception on failure
                                    );
                            break;
                        }

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

            if (newObject.Tags.Count == 0)
                throw new Exception("No tags found for this layer");	// TODO: create a proper exception class

            uint layer_index = 0;
            foreach (var layer in newObject.Layers)
            {
                layer.CalculateBoundingBox();
                newObject.CalculatePolygonNormals(layer.Points, layer.Polygons);
                newObject.ResolvePointPolygons(layer.Points, layer.Polygons);
                newObject.ResolvePolygonSurfaces(layer.Polygons, newObject.Tags, newObject.Surfaces, layer_index);
                newObject.CalculateVertexNormals(layer.Points, layer.Polygons);
                layer_index++;
            }

            return newObject;
        }
        // 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;
        }
        static LightwaveObject ReadObject2(ChunkReader reader)
        {
            // allocate an object and a default layer
            var newObject		= new LightwaveObject();
            var	currentLayer	= new Layer();
            newObject.Layers.Add(currentLayer);

            bool createdLayer	= false;
            uint pointOffset	= 0;
            uint polygonOffset	= 0;
            uint tagOffset		= 0;

            // process chunks as they're encountered
            while (reader.BytesLeft > 0)
            {
                var id = reader.ReadID<ChunkType>();
                var cksize	= reader.ReadUInt32();
                cksize += cksize & 1;

                using (var subchunkReader = reader.GetSubChunk(cksize))
                {
                    switch (id)
                    {
                        case ChunkType.ID_LAYR:
                        {
                            if (createdLayer)
                            {
                                currentLayer = new Layer();
                                newObject.Layers.Add(currentLayer);
                            }

                            createdLayer = true;
                            currentLayer.Index		= subchunkReader.ReadUInt16();
                            currentLayer.Flags		= subchunkReader.ReadUInt16();
                            currentLayer.pivot_x	= subchunkReader.ReadSingle();
                            currentLayer.pivot_y	= subchunkReader.ReadSingle();
                            currentLayer.pivot_z	= subchunkReader.ReadSingle();
                            currentLayer.Name		= subchunkReader.ReadString();
                            if (subchunkReader.BytesLeft > 2)
                                currentLayer.Parent = subchunkReader.ReadUInt16();
                            break;
                        }

                        case ChunkType.ID_PTAG:
                        {
                            ReadPolygonTags(subchunkReader, currentLayer.Polygons, polygonOffset, tagOffset);
                            break;
                        }

                        case ChunkType.ID_BBOX:
                        {
                            currentLayer.bbox_min_x = subchunkReader.ReadSingle();
                            currentLayer.bbox_min_y = subchunkReader.ReadSingle();
                            currentLayer.bbox_min_z = subchunkReader.ReadSingle();
                            currentLayer.bbox_max_x = subchunkReader.ReadSingle();
                            currentLayer.bbox_max_y = subchunkReader.ReadSingle();
                            currentLayer.bbox_max_z = subchunkReader.ReadSingle();
                            break;
                        }

                        case ChunkType.ID_PNTS:
                        {
                            pointOffset = (uint)currentLayer.Points.Count;
                            currentLayer.Points.AddRange(
                                        ReadPoints(subchunkReader)							// throws exception on failure
                                    );
                            break;
                        }

                        case ChunkType.ID_POLS:
                        {
                            polygonOffset = (uint)currentLayer.Polygons.Count;
                            currentLayer.Polygons.AddRange(
                                        ReadPolygons(subchunkReader, pointOffset)			// throws exception on failure
                                    );
                            break;
                        }

                        case ChunkType.ID_VMAP:
                        {
                            currentLayer.VertexMaps.Add(
                                        VertexMap.ReadVertexMap(subchunkReader, false)		// throws exception on failure
                                    );
                            break;
                        }

                        case ChunkType.ID_VMAD:
                        {
                            currentLayer.VertexMaps.Add(
                                        VertexMap.ReadVertexMap(subchunkReader, true)		// throws exception on failure
                                    );
                            break;
                        }

                        case ChunkType.ID_TAGS:
                        {
                            tagOffset = (uint)newObject.Tags.Count;
                            newObject.Tags.AddRange(
                                        LightwaveObject.ReadTags(subchunkReader)			// throws exception on failure
                                    );
                            break;
                        }

                        case ChunkType.ID_ENVL:
                        {
                            newObject.Envelopes.Add(
                                        Envelope.ReadEnvelope(subchunkReader)				// throws exception on failure
                                    );
                            break;
                        }

                        case ChunkType.ID_CLIP:
                        {
                            newObject.Clips.Add(
                                        Clip.ReadClip(subchunkReader)						// throws exception on failure
                                    );
                            break;
                        }

                        case ChunkType.ID_SURF:
                        {
                            newObject.Surfaces.Add(
                                        Surface.ReadSurface(subchunkReader)					// throws exception on failure
                                    );
                            break;
                        }

                        case ChunkType.ID_DESC: // Description Line - DESC { description-line[S0] }
                        case ChunkType.ID_TEXT: // Commentary Text - TEXT { comment[S0] }
                        case ChunkType.ID_ICON:	// Thumbnail Icon Image - ICON { encoding[U2], width[U2], data[U1] * }
                        case ChunkType.ID_VMPA: // Vertex Map Parameter - VMPA { UV subdivision type[I4], sketch color[I4] }
                            break;
                        default:
                            Console.WriteLine("Unknown chunk type " + reader.GetIDString((uint)id));
                            break;
                    }
                }
            }

            if (newObject.Tags.Count == 0)
                throw new Exception("No tags found for this layer");	// TODO: create a proper exception class

            uint layer_index = 0;
            foreach (var layer in newObject.Layers)
            {
                layer.CalculateBoundingBox();
                newObject.CalculatePolygonNormals(layer.Points, layer.Polygons);
                newObject.ResolvePointPolygons(layer.Points, layer.Polygons);
                newObject.ResolvePolygonSurfaces(layer.Polygons, newObject.Tags, newObject.Surfaces, layer_index);
                newObject.CalculateVertexNormals(layer.Points, layer.Polygons);
                newObject.ResolvePointVertexMaps(layer.Points, layer.VertexMaps);
                newObject.ResolvePolygonVertexMaps(layer.Polygons, layer.VertexMaps);
                layer_index++;
            }

            return newObject;
        }