public static Clip ReadClip(ChunkReader reader)
        {
            // index
            var index			= reader.ReadUInt32();

            // first subchunk header
            var type			= reader.ReadID<ClipType>();
            var subchunk_size	= reader.ReadUInt16();
            subchunk_size += (ushort)(subchunk_size & 1);

            Clip clip = null;
            using (var subChunkReader = reader.GetSubChunk(subchunk_size))
            {
                switch (type)
                {
                    case ClipType.ID_STIL:
                    {
                        var tmp_clip	= new ClipStill(index);
                        tmp_clip.Name = subChunkReader.ReadString();
                        clip = tmp_clip;
                        break;
                    }

                    case ClipType.ID_ISEQ:
                    {
                        var tmp_clip	= new ClipSequence(index);
                        tmp_clip.Digits = subChunkReader.ReadUInt8();
                        tmp_clip.Flags	= subChunkReader.ReadUInt8();
                        tmp_clip.Offset = subChunkReader.ReadSInt16();
                        subChunkReader.ReadUInt16();  // Legacy Cruft: Nothing to see here
                        tmp_clip.Start	= subChunkReader.ReadSInt16();
                        tmp_clip.End	= subChunkReader.ReadSInt16();
                        tmp_clip.Prefix = subChunkReader.ReadString();
                        tmp_clip.Suffix = subChunkReader.ReadString();
                        clip = tmp_clip;
                        break;
                    }

                    case ClipType.ID_ANIM:
                    {
                        var tmp_clip	= new ClipAnim(index);
                        tmp_clip.Name	= subChunkReader.ReadString();
                        tmp_clip.Server = subChunkReader.ReadString();
                        tmp_clip.Data	= subChunkReader.ReadBytes((uint)subChunkReader.BytesLeft);
                        clip = tmp_clip;
                        break;
                    }

                    case ClipType.ID_XREF:
                    {
                        var tmp_clip			= new ClipCloned(index);
                        tmp_clip.clip_reference_index	= subChunkReader.ReadUInt32();
                        tmp_clip.Name			= subChunkReader.ReadString();
                        clip = tmp_clip;
                        break;
                    }

                    case ClipType.ID_STCC:
                    {
                        var tmp_clip	= new ClipColorCycle(index);
                        tmp_clip.lo		= subChunkReader.ReadSInt16();
                        tmp_clip.hi		= subChunkReader.ReadSInt16();
                        tmp_clip.Name	= subChunkReader.ReadString();
                        clip = tmp_clip;
                        break;
                    }

                    default:
                        throw new Exception("Unknown Clip type"); // TODO: create proper exception class for this ...
                }
            }

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

                using (var subChunkReader = reader.GetSubChunk(subchunk_size))
                {
                    switch (id)
                    {
                        //case ClipDataType.ID_CLRS: // Color Space RGB   - CLRS { flags[U2], colorspace[U2], filename[FNAM0] }
                        //case ClipDataType.ID_CLRA: // Color Space Alpha - CLRA { flags[U2], colorspace[U2], filename[FNAM0] }
                        //case ClipDataType.ID_FILT: // Image Filtering   - FILT { flags[U2] }
                        //case ClipDataType.ID_DITH: // Image Dithering	  - DITH { flags[U2] }

                        // Contrast - CONT { contrast-delta[FP4], envelope[VX] }
                        case ClipDataType.ID_CONT: { clip.Contrast.value = subChunkReader.ReadSingle(); clip.Contrast.envelope_index = subChunkReader.ReadVariableLengthIndex(); break; }

                        // Brightness - BRIT { brightness-delta[FP4], envelope[VX] }
                        case ClipDataType.ID_BRIT: { clip.Brightness.value = subChunkReader.ReadSingle(); clip.Brightness.envelope_index = subChunkReader.ReadVariableLengthIndex(); break; }

                        // Saturation - SATR { saturation-delta[FP4], envelope[VX] }
                        case ClipDataType.ID_SATR: { clip.Saturation.value = subChunkReader.ReadSingle(); clip.Saturation.envelope_index = subChunkReader.ReadVariableLengthIndex(); break; }

                        // Hue - HUE { hue-rotation[FP4], envelope[VX] }
                        case ClipDataType.ID_HUE: { clip.Hue.value = subChunkReader.ReadSingle(); clip.Hue.envelope_index = subChunkReader.ReadVariableLengthIndex(); break; }

                        // Gamma Correction - GAMM { gamma[F4], envelope[VX] }
                        case ClipDataType.ID_GAMM: { clip.Gamma.value = subChunkReader.ReadSingle(); clip.Gamma.envelope_index = subChunkReader.ReadVariableLengthIndex(); break; }

                        // Negative - NEGA { enable[U2] }
                        case ClipDataType.ID_NEGA: { clip.Negative = subChunkReader.ReadUInt16() != 0; break; }

                        // Time - TIME { start-time[FP4], duration[FP4], frame-rate[FP4] }
                        case ClipDataType.ID_TIME:
                        {
                            clip.StartTime			= subChunkReader.ReadSingle();
                            clip.Duration			= subChunkReader.ReadSingle();
                            clip.FrameRate			= subChunkReader.ReadSingle();
                            break;
                        }

                        // Plug-in Image Filters - IFLT { server-name[S0], flags[U2], data[...] }
                        case ClipDataType.ID_IFLT:

                        // Plug-in Pixel Filters - PFLT { server-name[S0], flags[U2], data[...] }
                        case ClipDataType.ID_PFLT:
                        {
                            var filt = new LightwavePlugin();
                            filt.name	= subChunkReader.ReadString();
                            filt.flags	= subChunkReader.ReadUInt16();
                            filt.data	= subChunkReader.ReadBytes((uint)subChunkReader.BytesLeft);

                            if (id == ClipDataType.ID_IFLT)
                                clip.ImageFilters.Add(filt);
                            else
                                clip.PixelFilters.Add(filt);
                            break;
                        }

                        case ClipDataType.ID_FLAG: // not mentioned in documentation ...
                            var flags = subChunkReader.ReadUInt16(); // unknown what they mean ...
                            break;

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

            return clip;
        }
        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;
        }
        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;
        }
        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 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;
        }
        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;
        }
        // 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 Texture ReadTexture(ChunkReader reader, TextureType type)
        {
            var tex			= new Texture(type);
            var header_size = reader.ReadUInt16();
            using (var headerReader = reader.GetSubChunk(header_size))
            {
                // ordinal string
                tex.ord = headerReader.ReadString();

                // process subchunks as they're encountered
                while (headerReader.BytesLeft > 0)
                {
                    var subchunk_id		= headerReader.ReadID<TextureDataType>();
                    var subchunk_size	= headerReader.ReadUInt16();
                    subchunk_size += (ushort)(subchunk_size & 1);
                    using (var subChunkReader = headerReader.GetSubChunk(subchunk_size))
                    {
                        switch (subchunk_id)
                        {
                            case TextureDataType.ID_CHAN:
                                tex.Channel = subChunkReader.ReadID<TextureChannel>();
                                break;

                            case TextureDataType.ID_OPAC:
                                tex.opac_type = subChunkReader.ReadUInt16();
                                tex.opacity.value = subChunkReader.ReadSingle();
                                tex.opacity.envelope_index = subChunkReader.ReadVariableLengthIndex();
                                break;

                            case TextureDataType.ID_ENAB:
                                tex.enabled = subChunkReader.ReadUInt16();
                                break;

                            case TextureDataType.ID_NEGA:
                                tex.negative = subChunkReader.ReadUInt16();
                                break;

                            case TextureDataType.ID_AXIS:
                                tex.axis = subChunkReader.ReadUInt16();
                                break;

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

            if (reader.BytesLeft > 0)
            {
                using (var blockReader = reader.GetSubChunk((uint)reader.BytesLeft))
                {
                    switch (type)
                    {
                        case TextureType.ID_IMAP: tex.ReadImageMap(blockReader); break;
                        case TextureType.ID_PROC: tex.ReadProcedural(blockReader); break;
                        case TextureType.ID_GRAD: tex.ReadGradient(blockReader); break;
                        default:
                            Console.WriteLine("Unknown texture type " + reader.GetIDString((uint)type));
                            break;
                    }
                }
            }
            return tex;
        }
        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;
        }
示例#10
0
        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;
                }
            }
        }
示例#11
0
        // 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;
        }
示例#12
0
        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;
        }
示例#13
0
        public static LightwaveObject LoadObject(string filename)
        {
            // open the file
            var contents = File.ReadAllBytes(filename);
            using (var reader = new ChunkReader(contents))
            {
                // read the first 12 bytes
                var id			= reader.ReadID<HeaderID>();
                var formsize	= reader.ReadUInt32();
                var type		= reader.ReadID<ObjectType>();

                if (id != HeaderID.ID_FORM) // is this a LW object?
                    return null;

                using (var formReader = reader.GetSubChunk(formsize))
                {
                    switch (type)
                    {
                        case ObjectType.ID_LWO2: return ReadObject2(formReader);
                        case ObjectType.ID_LWOB: return ReadObject5(formReader);
                        default:
                            Console.WriteLine("Unknown object type " + reader.GetIDString((uint)type));
                            return null;
                    }
                }
            }
        }