protected override SolidMeshVertex GetVertex(BinaryReader reader, SolidObjectMaterial material, int stride)
        {
            SolidMeshVertex vertex;

            switch (stride)
            {
            case 60:
                vertex = new SolidMeshVertex
                {
                    Position     = BinaryUtil.ReadVector3(reader),
                    Normal       = BinaryUtil.ReadVector3(reader),
                    Color        = reader.ReadUInt32(),
                    TexCoords    = BinaryUtil.ReadVector2(reader),
                    BlendWeight  = BinaryUtil.ReadVector3(reader),
                    BlendIndices = BinaryUtil.ReadVector3(reader),
                };
                break;

            // position (12 bytes) + normal (12 bytes) + color (4 bytes) + tex coords (8 bytes)
            case 36:
                vertex = new SolidMeshVertex
                {
                    Position  = BinaryUtil.ReadVector3(reader),
                    Normal    = BinaryUtil.ReadVector3(reader),
                    Color     = reader.ReadUInt32(),
                    TexCoords = BinaryUtil.ReadVector2(reader)
                };
                break;

            // position (12 bytes) + color (4 bytes) + tex coords (8 bytes)
            case 24:
                vertex = new SolidMeshVertex
                {
                    Position  = BinaryUtil.ReadVector3(reader),
                    Color     = reader.ReadUInt32(),
                    TexCoords = BinaryUtil.ReadVector2(reader)
                };
                break;

            default:
                throw new Exception($"Cannot handle vertex size: {stride}");
            }

            return(vertex);
        }
Beispiel #2
0
        protected override SolidMeshVertex GetVertex(BinaryReader reader, SolidObjectMaterial material, int stride)
        {
            MostWantedMaterial mwm    = (MostWantedMaterial)material;
            SolidMeshVertex    vertex = new SolidMeshVertex();

            InternalEffectID id = (InternalEffectID)mwm.EffectId;

            switch (id)
            {
            case InternalEffectID.WorldNormalMap:
            case InternalEffectID.WorldReflectShader:
                vertex.Position             = BinaryUtil.ReadVector3(reader);
                vertex.Normal               = BinaryUtil.ReadVector3(reader);
                vertex.Color                = reader.ReadUInt32();
                vertex.TexCoords            = BinaryUtil.ReadVector2(reader);
                reader.BaseStream.Position += 8;     // TODO: What's this additional D3DDECLUSAGE_TEXCOORD element?
                vertex.Tangent              = BinaryUtil.ReadVector3(reader);
                reader.BaseStream.Position += 4;     // skip W component of tangent vector
                break;

            case InternalEffectID.skyshader:
                vertex.Position             = BinaryUtil.ReadVector3(reader);
                vertex.Normal               = BinaryUtil.ReadVector3(reader);
                vertex.Color                = reader.ReadUInt32();
                vertex.TexCoords            = BinaryUtil.ReadVector2(reader);
                reader.BaseStream.Position += 8;     // TODO: What's this additional D3DDECLUSAGE_TEXCOORD element?
                break;

            case InternalEffectID.WorldShader:
            case InternalEffectID.GlossyWindow:
            case InternalEffectID.billboardshader:
            case InternalEffectID.CarShader:
                vertex.Position  = BinaryUtil.ReadVector3(reader);
                vertex.Normal    = BinaryUtil.ReadVector3(reader);
                vertex.Color     = reader.ReadUInt32();
                vertex.TexCoords = BinaryUtil.ReadVector2(reader);
                break;

            default:
                throw new Exception($"Unsupported effect in object {Name}: {id}");
            }

            return(vertex);
        }
Beispiel #3
0
        /// <summary>
        /// Process the model data.
        /// </summary>
        public virtual void PostProcessing()
        {
            var vertexBuffersCount = VertexBuffers.Count;

            // Bookkeeping array: how many vertices are in each vertex buffer?
            var vbCounts = new uint[vertexBuffersCount];

            // Bookkeeping array: how many vertices have been consumed from each vertex buffer?
            var vbOffsets = new uint[vertexBuffersCount];

            // Vertex buffer data readers
            var vbReaders = new BinaryReader[vertexBuffersCount];

            // Vertex arrays
            var vbArrays = new SolidMeshVertex[vertexBuffersCount][];

            // Set up vertex buffer readers
            for (var i = 0; i < vbReaders.Length; i++)
            {
                vbReaders[i] = new BinaryReader(new MemoryStream(VertexBuffers[i]));
            }

            // Filling in vbCounts...
            if (vertexBuffersCount == 1)
            {
                // The mesh descriptor tells us how many vertices exist.
                // Since we only have one vertex buffer, we can use the info
                // from the mesh descriptor instead of doing the material loop
                // seen after this block.
                vbCounts[0] = MeshDescriptor.NumVerts;
            }
            else if (vertexBuffersCount > 1)
            {
                // Fill in vbCounts by examining every material.
                // We need to make sure at least one of our materials
                // actually has a NumVerts > 0, otherwise weird things
                // will probably happen.
                Debug.Assert(Materials.Any(m => m.NumVerts > 0));
                foreach (var t in Materials)
                {
                    vbCounts[t.VertexStreamIndex] += t.NumVerts;
                }
            }
            else
            {
                // If we have no vertex buffers, we can bail out.
                return;
            }

            // Verifying the integrity of our data...
            for (var i = 0; i < vbReaders.Length; i++)
            {
                // To avoid a division-by-zero exception, we allow a vertex buffer to have
                // EITHER 0 vertices OR exactly enough data for a given number of vertices.
                Debug.Assert(vbCounts[i] == 0 || VertexBuffers[i].Length % vbCounts[i] == 0);

                vbArrays[i] = new SolidMeshVertex[vbCounts[i]];
            }

            // Reading vertices from buffers...
            foreach (var solidObjectMaterial in Materials)
            {
                var vbIndex       = solidObjectMaterial.VertexStreamIndex;
                var vbStream      = vbReaders[vbIndex];
                var vbVertexCount = vbCounts[vbIndex];
                var vbOffset      = vbOffsets[vbIndex];
                var vbStride      = (int)(vbStream.BaseStream.Length / vbVertexCount);

                var numVerts = solidObjectMaterial.NumVerts == 0
                    ? vbVertexCount
                    : solidObjectMaterial.NumVerts;

                if (vbOffset < vbVertexCount)
                {
                    var vbStartPos = vbStream.BaseStream.Position;

                    for (var i = 0; i < numVerts; i++)
                    {
                        Debug.Assert(vbStream.BaseStream.Position - vbStartPos == vbStride * i, "vbStream.BaseStream.Position - vbStartPos == vbStride * i");
                        vbArrays[vbIndex][vbOffset + i] = GetVertex(vbStream, solidObjectMaterial, vbStride);
                    }

                    vbOffsets[vbIndex] += numVerts;
                }
                else if (vbOffset != vbVertexCount)
                {
                    throw new Exception($"Vertex buffer read is in a weird state. vbOffset={vbOffset} vbVertexCount={vbVertexCount}");
                }
            }

            for (var i = 0; i < vbArrays.Length; i++)
            {
                ProcessVertices(ref vbArrays[i], i);
            }

            // Loading vertices into materials...
            foreach (var solidObjectMaterial in Materials)
            {
                var vertexStreamIndex = solidObjectMaterial.VertexStreamIndex;

                if (solidObjectMaterial.Indices.Any())
                {
                    var meshVertices        = vbArrays[vertexStreamIndex];
                    var maxReferencedVertex = solidObjectMaterial.Indices.Max();
                    solidObjectMaterial.Vertices = new SolidMeshVertex[maxReferencedVertex + 1];

                    for (var j = 0; j <= maxReferencedVertex; j++)
                    {
                        solidObjectMaterial.Vertices[j] = meshVertices[j];
                    }

                    // Validate material indices
                    Debug.Assert(solidObjectMaterial.Indices.All(t => t < solidObjectMaterial.Vertices.Length));
                }
                else
                {
                    solidObjectMaterial.Vertices = Array.Empty <SolidMeshVertex>();
                }
            }

            // Clean up vertex buffers, which are no longer needed
            VertexBuffers.Clear();
        }
        protected override SolidMeshVertex GetVertex(BinaryReader reader, SolidObjectMaterial material, int stride)
        {
            World15Material wm     = (World15Material)material;
            SolidMeshVertex vertex = new SolidMeshVertex();

            InternalEffectID id = (InternalEffectID)wm.EffectId;

            switch (id)
            {
            case InternalEffectID.WorldShader:
            case InternalEffectID.GLASS_REFLECT:
            case InternalEffectID.WorldZBiasShader:
            case InternalEffectID.Tree:
            case InternalEffectID.WATER:
                vertex.Position  = BinaryUtil.ReadVector3(reader);
                vertex.Normal    = BinaryUtil.ReadNormal(reader, true);
                vertex.TexCoords = BinaryUtil.ReadVector2(reader);
                vertex.Color     = reader.ReadUInt32(); // daytime color
                reader.ReadUInt32();                    // nighttime color
                break;

            case InternalEffectID.WorldPrelitShader:
                vertex.Position = BinaryUtil.ReadVector3(reader);
                vertex.Color    = reader.ReadUInt32(); // daytime color
                reader.ReadUInt32();                   // nighttime color
                vertex.TexCoords = BinaryUtil.ReadVector2(reader);
                break;

            case InternalEffectID.WorldZBiasPrelitShader:
                vertex.Position = BinaryUtil.ReadVector3(reader);
                vertex.Normal   = BinaryUtil.ReadNormal(reader, true);
                vertex.Color    = reader.ReadUInt32(); // daytime color
                reader.ReadUInt32();                   // nighttime color
                vertex.TexCoords = BinaryUtil.ReadVector2(reader);
                break;

            case InternalEffectID.WorldNormalMap:
            case InternalEffectID.GLASS_REFLECTNM:
            case InternalEffectID.WorldRoadShader:
            case InternalEffectID.WorldFEShader:
                vertex.Position  = BinaryUtil.ReadVector3(reader);
                vertex.Normal    = BinaryUtil.ReadNormal(reader, true);
                vertex.TexCoords = BinaryUtil.ReadVector2(reader);
                vertex.Color     = reader.ReadUInt32(); // daytime color
                reader.ReadUInt32();                    // nighttime color
                vertex.Tangent = BinaryUtil.ReadNormal(reader, true);
                break;

            case InternalEffectID.CarShader:
            case InternalEffectID.CARNORMALMAP:
                vertex.Position  = BinaryUtil.ReadNormal(reader, true) * 8;
                vertex.TexCoords = new Vector2(reader.ReadInt16() / 4096f, reader.ReadInt16() / 4096f - 1);
                vertex.Color     = reader.ReadUInt32();
                vertex.Normal    = BinaryUtil.ReadNormal(reader, true);
                vertex.Tangent   = BinaryUtil.ReadNormal(reader, true);
                break;

            default:
                throw new Exception($"Unsupported effect in object {Name}: {id}");
            }

            return(vertex);
        }
Beispiel #5
0
        /// <inheritdoc />
        /// <summary>
        /// This thing is responsible for somehow deriving proper vertex data from
        /// a NFS:Undercover vertex buffer. Getting the coordinates is easy, but
        /// the hard part is getting the texture coordinates. Can it be done? Who knnows?
        /// </summary>
        /// <remarks>And I didn't even mention the fact that there are wayyyy too many edge cases...</remarks>
        /// <remarks>I hate this game. Worse than E.T.</remarks>
        /// <param name="reader"></param>
        /// <param name="material"></param>
        /// <param name="stride"></param>
        /// <returns></returns>
        protected override SolidMeshVertex GetVertex(BinaryReader reader, SolidObjectMaterial material, int stride)
        {
            var             effectId = ((UndercoverMaterial)material).EffectId;
            SolidMeshVertex vertex   = new SolidMeshVertex();

            switch (effectId)
            {
            case EffectID.mw2_constant:
            case EffectID.mw2_constant_alpha_bias:
            case EffectID.mw2_illuminated:
            case EffectID.mw2_pano:
                vertex.Position = BinaryUtil.ReadVector3(reader);
                reader.ReadSingle();
                vertex.TexCoords = BinaryUtil.ReadShort2N(reader) * 32;
                break;

            case EffectID.mw2_matte:
            case EffectID.mw2_diffuse_spec:
            case EffectID.mw2_branches:
            case EffectID.mw2_glass_no_n:
            case EffectID.mw2_diffuse_spec_illum:
            case EffectID.diffuse_spec_2sided:
            case EffectID.mw2_combo_refl:
            case EffectID.mw2_dirt:
            case EffectID.mw2_grass:
            case EffectID.mw2_tunnel_illum:
            case EffectID.mw2_diffuse_spec_alpha:
            case EffectID.mw2_trunk:
                vertex.Position = BinaryUtil.ReadVector3(reader);
                reader.ReadSingle();
                vertex.Color     = reader.ReadUInt32();
                vertex.TexCoords = BinaryUtil.ReadShort2N(reader) * 32;
                vertex.Normal    = BinaryUtil.ReadNormal(reader, true);
                break;

            case EffectID.mw2_normalmap:
            case EffectID.mw2_normalmap_bias:
            case EffectID.mw2_glass_refl:
            case EffectID.normalmap2sided:
            case EffectID.mw2_road_overlay:
            case EffectID.mw2_road_refl_overlay:
            case EffectID.mw2_rock:
                vertex.Position = BinaryUtil.ReadVector3(reader);
                reader.ReadSingle();
                vertex.Color     = reader.ReadUInt32();
                vertex.TexCoords = BinaryUtil.ReadShort2N(reader) * 32;
                vertex.Normal    = BinaryUtil.ReadNormal(reader, true);
                // todo: read packed tangent vector
                reader.BaseStream.Position += 0x8;
                break;

            case EffectID.mw2_grass_rock:
            case EffectID.mw2_road_refl:
            case EffectID.mw2_road_refl_tile:
            case EffectID.mw2_road_tile:
            case EffectID.mw2_dirt_rock:
                vertex.Position = BinaryUtil.ReadVector3(reader);
                reader.ReadSingle();
                vertex.Color     = reader.ReadUInt32();
                vertex.TexCoords = BinaryUtil.ReadShort2N(reader) * 32;
                // TODO: TEXCOORD1??? what do we do with this?
                reader.ReadInt16();
                reader.ReadInt16();
                vertex.Normal = BinaryUtil.ReadNormal(reader, true);
                // todo: read packed tangent vector
                reader.BaseStream.Position += 0x8;
                break;

            case EffectID.mw2_tunnel_road:
                vertex.Position = BinaryUtil.ReadVector3(reader);
                reader.ReadSingle();
                vertex.Color     = reader.ReadUInt32();
                vertex.TexCoords = BinaryUtil.ReadShort2N(reader) * 32;
                // TODO: TEXCOORD1??? what do we do with this?
                reader.ReadInt16();
                reader.ReadInt16();
                // TODO: TEXCOORD2??? what do we do with this?
                reader.ReadInt16();
                reader.ReadInt16();
                vertex.Normal = BinaryUtil.ReadNormal(reader, true);
                // todo: read packed tangent vector
                reader.BaseStream.Position += 0x8;
                break;

            case EffectID.mw2_road_refl_lite:
            case EffectID.mw2_road_lite:
            case EffectID.mw2_grass_dirt:
                vertex.Position = BinaryUtil.ReadVector3(reader);
                reader.ReadSingle();
                vertex.Color     = reader.ReadUInt32();
                vertex.TexCoords = BinaryUtil.ReadShort2N(reader) * 32;
                // TODO: TEXCOORD1??? what do we do with this?
                reader.ReadInt16();
                reader.ReadInt16();
                vertex.Normal = BinaryUtil.ReadNormal(reader, true);
                break;

            case EffectID.mw2_tunnel_wall:
                vertex.Position = BinaryUtil.ReadVector3(reader);
                reader.ReadSingle();
                vertex.Color     = reader.ReadUInt32();
                vertex.TexCoords = BinaryUtil.ReadShort2N(reader) * 32;
                // TODO: TEXCOORD1??? what do we do with this?
                reader.ReadInt16();
                reader.ReadInt16();
                break;

            case EffectID.mw2_matte_alpha:
            case EffectID.mw2_dif_spec_a_bias:
            case EffectID.mw2_dirt_overlay:
                vertex.Position = BinaryUtil.ReadVector3(reader);
                reader.ReadSingle();
                vertex.Color     = reader.ReadUInt32();
                vertex.TexCoords = BinaryUtil.ReadShort2N(reader) * 32;
                vertex.Normal    = BinaryUtil.ReadNormal(reader, true);
                break;

            case EffectID.mw2_foliage:
            case EffectID.mw2_foliage_lod:
            case EffectID.mw2_scrub:
            case EffectID.mw2_scrub_lod:
            case EffectID.mw2_ocean:
                vertex.Position = BinaryUtil.ReadVector3(reader);
                reader.ReadSingle();
                vertex.Color     = reader.ReadUInt32();
                vertex.TexCoords = BinaryUtil.ReadShort2N(reader) * 32;
                break;

            case EffectID.mw2_sky:
                vertex.Position = BinaryUtil.ReadVector3(reader);
                reader.ReadSingle();
                vertex.TexCoords = BinaryUtil.ReadVector2(reader);
                break;

            case EffectID.mw2_texture_scroll:
            case EffectID.mw2_car_heaven_default:
                vertex.Position = BinaryUtil.ReadVector3(reader);
                reader.ReadSingle();
                // TODO: COLOR0 is a float4. how do we deal with that?
                reader.BaseStream.Position += 0x10;
                vertex.TexCoords            = BinaryUtil.ReadVector2(reader);
                break;

            case EffectID.mw2_car_heaven:
                vertex.Position = BinaryUtil.ReadVector3(reader);
                reader.ReadSingle();
                // TODO: COLOR0 is a float4. how do we deal with that?
                reader.BaseStream.Position += 0x10;
                vertex.TexCoords            = BinaryUtil.ReadVector2(reader);
                vertex.Normal = BinaryUtil.ReadNormal(reader, true);
                break;

            case EffectID.mw2_carhvn_floor:
            case EffectID.mw2_road:
                vertex.Position = BinaryUtil.ReadVector3(reader);
                reader.ReadSingle();
                vertex.Color     = reader.ReadUInt32();
                vertex.TexCoords = BinaryUtil.ReadShort2N(reader) * 32;
                // TODO: TEXCOORD1??? what do we do with this?
                reader.ReadInt16();
                reader.ReadInt16();
                vertex.Normal = BinaryUtil.ReadNormal(reader, true);
                // todo: read packed tangent vector
                reader.BaseStream.Position += 0x8;
                break;

            case EffectID.car:
            case EffectID.car_a:
            case EffectID.car_a_nzw:
            case EffectID.car_nm:
            case EffectID.car_nm_a:
            case EffectID.car_nm_v_s:
            case EffectID.car_nm_v_s_a:
            case EffectID.car_si:
            case EffectID.car_si_a:
            case EffectID.car_t:
            case EffectID.car_t_a:
            case EffectID.car_t_nm:
            case EffectID.car_v:
                vertex.Position  = BinaryUtil.ReadNormal(reader, true) * 10;
                vertex.TexCoords = BinaryUtil.ReadShort2N(reader) * 32;
                vertex.Color     = reader.ReadUInt32();
                vertex.Normal    = BinaryUtil.ReadNormal(reader, true);
                vertex.Tangent   = BinaryUtil.ReadNormal(reader, true);
                break;

            default:
                throw new Exception($"Unsupported effect: {effectId}");
            }

            return(vertex);
        }
Beispiel #6
0
        protected override SolidMeshVertex GetVertex(BinaryReader reader, SolidObjectMaterial material, int stride)
        {
            ProStreetMaterial psm    = (ProStreetMaterial)material;
            SolidMeshVertex   vertex = new SolidMeshVertex();

            switch ((EffectID)psm.EffectId)
            {
            case EffectID.WORLDBAKEDLIGHTING:
                vertex.Position             = BinaryUtil.ReadVector3(reader);
                vertex.Color                = reader.ReadUInt32();
                vertex.TexCoords            = BinaryUtil.ReadVector2(reader);
                reader.BaseStream.Position += 16;
                vertex.Normal               = BinaryUtil.ReadNormal(reader, true);
                break;

            case EffectID.WORLD:
            case EffectID.SKY:
                vertex.Position             = BinaryUtil.ReadVector3(reader);
                vertex.Normal               = BinaryUtil.ReadNormal(reader);
                vertex.Color                = reader.ReadUInt32();
                vertex.TexCoords            = BinaryUtil.ReadVector2(reader);
                reader.BaseStream.Position += 8;     // TODO: what is this second D3DDECLUSAGE_TEXCOORD element?
                break;

            case EffectID.WORLDNORMALMAP:
                vertex.Position             = BinaryUtil.ReadVector3(reader);
                vertex.Normal               = BinaryUtil.ReadNormal(reader);
                vertex.Color                = reader.ReadUInt32();
                vertex.TexCoords            = BinaryUtil.ReadVector2(reader);
                reader.BaseStream.Position += 8;     // TODO: what is this second D3DDECLUSAGE_TEXCOORD element?
                // TODO: read tangent
                reader.BaseStream.Position += 16;
                reader.BaseStream.Position +=
                    8;     // TODO: what are these other D3DDECLUSAGE_TEXCOORD elements? (2x D3DDECLTYPE_UBYTE4)
                break;

            case EffectID.WorldDepthShader:
                vertex.Position  = BinaryUtil.ReadVector3(reader);
                vertex.Normal    = BinaryUtil.ReadNormal(reader);
                vertex.Color     = reader.ReadUInt32();
                vertex.TexCoords = BinaryUtil.ReadVector2(reader);
                break;

            case EffectID.TREELEAVES:
                vertex.Position = BinaryUtil.ReadVector3(reader);
                vertex.Color    = reader.ReadUInt32();
                vertex.Normal   = BinaryUtil.ReadNormal(reader);
                // TODO: COLLADA supports tangent vectors, so we should eventually read them
                //> elements[3]: type=D3DDECLTYPE_FLOAT4 usage=D3DDECLUSAGE_TANGENT size=16 offset=0x1c
                reader.BaseStream.Position += 0x10;
                vertex.TexCoords            = BinaryUtil.ReadVector2(reader);
                break;

            case EffectID.WORLDCONSTANT:
                vertex.Position  = BinaryUtil.ReadVector3(reader);
                vertex.TexCoords = BinaryUtil.ReadVector2(reader);
                vertex.Color     = reader.ReadUInt32();
                break;

            case EffectID.ROAD:
                vertex.Position             = BinaryUtil.ReadVector3(reader);
                vertex.TexCoords            = BinaryUtil.ReadVector2(reader);
                reader.BaseStream.Position += 28;
                vertex.Color = reader.ReadUInt32();
                break;

            case EffectID.TERRAIN:
                vertex.Position             = BinaryUtil.ReadVector3(reader);
                vertex.TexCoords            = BinaryUtil.ReadVector2(reader);
                reader.BaseStream.Position += 16;
                vertex.Normal = BinaryUtil.ReadNormal(reader, true);
                // TODO: read packed tangent vector (4 short-components)
                reader.BaseStream.Position += 8;
                vertex.Color = reader.ReadUInt32();
                reader.BaseStream.Position +=
                    8;     // TODO: what are these other D3DDECLUSAGE_TEXCOORD elements? (2x D3DDECLTYPE_UBYTE4)
                break;

            case EffectID.GRASSTERRAIN:
                vertex.Position             = BinaryUtil.ReadVector3(reader);
                vertex.TexCoords            = BinaryUtil.ReadVector2(reader);
                reader.BaseStream.Position += 20;
                vertex.Normal = BinaryUtil.ReadNormal(reader, true);
                // TODO: read packed tangent vector (4 short-components)
                reader.BaseStream.Position += 8;
                vertex.Color = reader.ReadUInt32();
                break;

            case EffectID.FLAG:
                vertex.Position             = BinaryUtil.ReadVector3(reader);
                vertex.Color                = reader.ReadUInt32();
                vertex.Normal               = BinaryUtil.ReadNormal(reader, true);
                vertex.TexCoords            = BinaryUtil.ReadVector2(reader);
                reader.BaseStream.Position += 8;     // TODO: what is this second D3DDECLUSAGE_TEXCOORD element?
                break;

            case EffectID.GRASSCARD:
                vertex.Position = BinaryUtil.ReadVector3(reader);
                vertex.Color    = reader.ReadUInt32();
                vertex.Normal   = BinaryUtil.ReadNormal(reader, true);
                // TODO: read packed tangent vector (4 short-components)
                reader.BaseStream.Position += 8;
                vertex.TexCoords            = BinaryUtil.ReadVector2(reader);
                reader.BaseStream.Position += 8;     // TODO: what is this second D3DDECLUSAGE_TEXCOORD element?
                break;

            case EffectID.CAR:
            case EffectID.CARNORMALMAP:
            case EffectID.CARVINYL:
                vertex.Position  = BinaryUtil.ReadNormal(reader, true);
                vertex.TexCoords = BinaryUtil.ReadShort2N(reader);
                vertex.Color     = reader.ReadUInt32();
                vertex.Normal    = BinaryUtil.ReadNormal(reader, true);
                vertex.Tangent   = BinaryUtil.ReadNormal(reader, true);
                break;

            case EffectID.ALWAYSFACING:
                vertex.Position = BinaryUtil.ReadVector3(reader);
                vertex.Color    = reader.ReadUInt32();
                vertex.Normal   = BinaryUtil.ReadNormal(reader, true);
                // TODO: read packed tangent vector (4 short-components)
                reader.BaseStream.Position += 8;
                vertex.TexCoords            = BinaryUtil.ReadVector2(reader);
                break;

            case EffectID.STANDARD:
                vertex.Position             = BinaryUtil.ReadVector3(reader);
                vertex.Color                = reader.ReadUInt32();
                vertex.TexCoords            = BinaryUtil.ReadVector2(reader);
                reader.BaseStream.Position += 8;     // todo: what's this?
                break;

            case EffectID.TUNNEL:
                vertex.Position             = BinaryUtil.ReadVector3(reader);
                vertex.TexCoords            = BinaryUtil.ReadVector2(reader);
                reader.BaseStream.Position += 8;     // todo: what's this?
                reader.BaseStream.Position += 4;     // todo: what's this?
                reader.BaseStream.Position += 4;     // todo: what's this?
                vertex.Color  = reader.ReadUInt32();
                vertex.Normal = BinaryUtil.ReadNormal(reader, true);
                break;

            case EffectID.ROADLIGHTMAP:
                vertex.Position             = BinaryUtil.ReadVector3(reader);
                vertex.TexCoords            = BinaryUtil.ReadVector2(reader);
                reader.BaseStream.Position += 8;     // todo: what's this?
                reader.BaseStream.Position += 8;     // todo: what's this?
                reader.BaseStream.Position += 4;     // todo: what's this?
                vertex.Normal = BinaryUtil.ReadNormal(reader, true);
                vertex.Color  = reader.ReadUInt32();
                break;

            case EffectID.WATER:
                vertex.Position  = BinaryUtil.ReadVector3(reader);
                vertex.Color     = reader.ReadUInt32();
                vertex.TexCoords = BinaryUtil.ReadVector2(reader);
                break;

            case EffectID.WORLDBONE:
                vertex.Position     = BinaryUtil.ReadVector3(reader);
                vertex.Normal       = BinaryUtil.ReadNormal(reader);
                vertex.Color        = reader.ReadUInt32();
                vertex.TexCoords    = BinaryUtil.ReadVector2(reader);
                vertex.BlendWeight  = BinaryUtil.ReadVector3(reader);
                vertex.BlendIndices = BinaryUtil.ReadVector3(reader);
                vertex.Tangent      = BinaryUtil.ReadVector3(reader);
                break;

            default:
                throw new Exception($"Unsupported effect in object {Name}: {psm.EffectId}");
            }

            return(vertex);
        }