override public void FinalizeSolitaryBrush()
        {
            MasterBrush geom      = m_Geometry;
            int         iNumVerts = GetNumUsedVerts();
            int         iNumTris  = iNumVerts; // Happens to be true for QuadStripBrush and descendants

            MeshFilter mf = GetComponent <MeshFilter>();

            mf.mesh.Clear(false);

            mf.mesh.vertices  = SubsetOf(geom.m_Vertices, iNumVerts);
            mf.mesh.triangles = SubsetOf(geom.m_Tris, iNumTris);
            mf.mesh.normals   = SubsetOf(geom.m_Normals, iNumVerts);
            if (geom.VertexLayout.Value.texcoord0.size == 3)
            {
                mf.mesh.SetUVs(0, geom.m_UVWs.GetRange(0, iNumVerts));
            }
            else
            {
                mf.mesh.uv = SubsetOf(geom.m_UVs, iNumVerts);
            }
            mf.mesh.colors32 = SubsetOf(geom.m_Colors, iNumVerts);
            mf.mesh.tangents = SubsetOf(geom.m_Tangents, iNumVerts);

            mf.mesh.RecalculateBounds();
            MasterBrush.Pool.PutAndClear(ref m_Geometry);
        }
        override public void ApplyChangesToVisuals()
        {
            MeshFilter  mf       = GetComponent <MeshFilter>();
            MasterBrush geometry = m_Geometry;

            mf.mesh.vertices = geometry.m_Vertices;
            mf.mesh.normals  = geometry.m_Normals;
            mf.mesh.colors32 = geometry.m_Colors;
            mf.mesh.uv       = geometry.m_UVs;
            mf.mesh.tangents = geometry.m_Tangents;
            mf.mesh.RecalculateBounds();
        }
Exemple #3
0
        /// Returns a new subset containing the passed geometry.
        /// raises ArgumentOutOfRangeException if not enough room.
        /// Pass:
        ///   rMasterBrush - Geometry, all of which will be copied into the new subset
        public BatchSubset AddSubset(int nVert, int nTris, MasterBrush rMasterBrush)
        {
            SelfCheck();
            // If we're not empty, the caller should never have tried to add the subset,
            // because it's caller's responsibility to check HasSpaceFor().
            // If we're empty, allow anything (up to the Unity limit).
            if (!HasSpaceFor(nVert) && (m_Geometry.NumVerts > 0))
            {
                throw new ArgumentOutOfRangeException("nVert");
            }
            if (m_Geometry.NumVerts == 0)
            {
                m_Geometry.Layout = rMasterBrush.VertexLayout.Value;
            }

            BatchSubset child = new BatchSubset();

            child.m_ParentBatch = this;
            m_Groups.Add(child);
            child.m_Active         = true;
            child.m_StartVertIndex = m_Geometry.NumVerts;
            child.m_VertLength     = nVert;
            child.m_iTriIndex      = m_Geometry.NumTriIndices;
            child.m_nTriIndex      = nTris;
            // This is normally true -- unless the geometry has been welded
            // Debug.Assert(nVert % 3 == 0);
            child.m_Bounds = GetBoundsFor(rMasterBrush.m_Vertices, 0, nVert);

            if (nVert > 0)
            {
                m_Geometry.EnsureGeometryResident();
                AppendVertexData(nVert, rMasterBrush.m_Vertices,
                                 rMasterBrush.m_Normals,
                                 rMasterBrush.m_UVs,
                                 rMasterBrush.m_UVWs,
                                 rMasterBrush.m_Colors,
                                 rMasterBrush.m_Tangents);
                AppendTriangleData(child.m_StartVertIndex, child.m_nTriIndex, rMasterBrush.m_Tris);
                DelayedUpdateMesh();
            }

            SelfCheck();
            return(child);
        }
        protected override void InitBrush(BrushDescriptor desc, TrTransform localPointerXf)
        {
            base.InitBrush(desc, localPointerXf);
            m_Geometry = MasterBrush.Pool.Get();
            Debug.Assert(m_Geometry.VertexLayout == null, "m_Geometry is not fully reset");
            m_Geometry.VertexLayout = GetVertexLayout(desc);
            m_LastQuadRight         = Vector3.zero;

            m_NumQuads = m_Geometry.NumVerts / 6;

            MeshFilter mf = GetComponent <MeshFilter>();

            mf.mesh = null; // Force a new, empty, mf-owned mesh to be generated
            mf.mesh.MarkDynamic();

            // We only need to set verts and tris here because the mesh is zeroed out, effectively hidden
            mf.mesh.vertices  = m_Geometry.m_Vertices;
            mf.mesh.triangles = m_Geometry.m_Tris;
        }
        override public void ApplyChangesToVisuals()
        {
            UnityEngine.Profiling.Profiler.BeginSample("QuadStripBrushStretchUV.ApplyChangesToVisuals");
            FlushUpdateUVRequest();

            MeshFilter  mf       = GetComponent <MeshFilter>();
            MasterBrush geometry = m_Geometry;

            mf.mesh.vertices = geometry.m_Vertices;
            mf.mesh.normals  = geometry.m_Normals;
            mf.mesh.colors32 = geometry.m_Colors;
            mf.mesh.tangents = geometry.m_Tangents;
            if (m_StoreWidthInTexcoord0Z)
            {
                mf.mesh.SetUVs(0, geometry.m_UVWs);
            }
            else
            {
                mf.mesh.uv = geometry.m_UVs;
            }
            mf.mesh.RecalculateBounds();
            UnityEngine.Profiling.Profiler.EndSample();
        }
        override public BatchSubset FinalizeBatchedBrush()
        {
            int numVerts = GetNumUsedVerts();
            int numTris  = numVerts;
            var geometry = m_Geometry;

            m_Geometry = null;

            // The Weld function only supports single-sided geometry; but this is fine since
            // there should be no double-sided QuadStrip brushes left.
            if (!m_EnableBackfaces)
            {
                int newNumVerts, newNumTris;
                WeldSingleSidedQuadStrip(
                    GetVertexLayout(m_Desc), geometry, numVerts,
                    out newNumVerts, out newNumTris);
                var ret = Canvas.BatchManager.CreateSubset(m_Desc, newNumVerts, newNumTris, geometry);

                // Put the triangles back to how they were because MasterBrush doesn't expect anyone
                // to modify m_Tris. We should really get rid of MasterBrush and use GeometryPool
                // everywhere. Alternatively, if every QuadStripBrush goes through the vertex-reduction
                // pathway then we can leave m_Tris trashed, since the vertex-reduction step always
                // writes new indices!

                // For MasterBrush, # verts === # indices, since it assumes each tri uses 3 unique verts.
                for (int i = 0; i < numVerts; ++i)
                {
                    geometry.m_Tris[i] = i;
                }
                MasterBrush.Pool.PutAndClear(ref geometry);
                return(ret);
            }

            return(Canvas.BatchManager.CreateSubset(
                       m_Desc, numVerts, numTris, geometry));
        }
        // iSegmentBack       quad index of segment, trailing edge
        // iSegmentFront      quad index of segment, leading edge
        // "solid" is my term for "the thing comprised of a frontface and backface quad"
        protected void FlushUpdateUVRequest()
        {
            MasterBrush rMasterBrush = m_Geometry;

            if (!m_UpdateUVRequest.IsValid)
            {
                return;
            }
            int iSegmentBack  = m_UpdateUVRequest.back;
            int iSegmentFront = m_UpdateUVRequest.front;

            m_UpdateUVRequest.Clear();

            int quadsPerSolid = m_EnableBackfaces ? 2 : 1;
            int numSolids     = (iSegmentFront - iSegmentBack) / quadsPerSolid;

            float fYStart, fYEnd;
            {
                float random01 = m_rng.In01(iSegmentBack * 6);
                int   numV     = m_Desc.m_TextureAtlasV;
                int   iAtlas   = (int)(random01 * numV);
                fYStart = (iAtlas) / (float)numV;
                fYEnd   = (iAtlas + 1) / (float)numV;
            }

            //get length of current segment
            float fSegmentLength = 0.0f;

            for (int iSolid = 0; iSolid < numSolids; ++iSolid)
            {
                fSegmentLength += m_QuadLengths[iSegmentBack + (iSolid * quadsPerSolid)];
            }
            // Just enough to get rid of NaNs. If length is 0, doesn't really matter what UVs are
            if (fSegmentLength == 0)
            {
                fSegmentLength = 1;
            }

            //then, run back through the last segment and update our UVs
            float fRunningLength = 0.0f;

            for (int iSolid = 0; iSolid < numSolids; ++iSolid)
            {
                int   iQuadIndex      = iSegmentBack + (iSolid * quadsPerSolid);
                int   iVertIndex      = iQuadIndex * 6;
                float thisSolidLength = m_QuadLengths[iQuadIndex]; // assumes frontface == backface length
                float fXStart         = fRunningLength / fSegmentLength;
                float fXEnd           = (fRunningLength + thisSolidLength) / fSegmentLength;
                fRunningLength += thisSolidLength;

                rMasterBrush.m_UVs[iVertIndex].Set(fXStart, fYStart);
                rMasterBrush.m_UVs[iVertIndex + 1].Set(fXEnd, fYStart);
                rMasterBrush.m_UVs[iVertIndex + 2].Set(fXStart, fYEnd);
                rMasterBrush.m_UVs[iVertIndex + 3].Set(fXStart, fYEnd);
                rMasterBrush.m_UVs[iVertIndex + 4].Set(fXEnd, fYStart);
                rMasterBrush.m_UVs[iVertIndex + 5].Set(fXEnd, fYEnd);

                if (m_StoreWidthInTexcoord0Z)
                {
                    for (int i = 0; i < 6; i++)
                    {
                        rMasterBrush.m_UVWs[iVertIndex + i] = new Vector3(
                            rMasterBrush.m_UVs[iVertIndex + i].x,
                            rMasterBrush.m_UVs[iVertIndex + i].y
                            );
                    }
                }
            }

            // Update tangent space
            ComputeTangentSpaceForQuads(
                rMasterBrush.m_Vertices,
                rMasterBrush.m_UVs,
                rMasterBrush.m_Normals,
                rMasterBrush.m_Tangents,
                quadsPerSolid * 6,
                iSegmentBack * 6,
                iSegmentFront * 6);

            if (m_StoreWidthInTexcoord0Z)
            {
                Vector3 uvw;
                for (int iSolid = 0; iSolid < numSolids; ++iSolid)
                {
                    int   iQuadIndex = iSegmentBack + (iSolid * quadsPerSolid);
                    int   iVertIndex = iQuadIndex * 6;
                    float width      = (rMasterBrush.m_Vertices[iVertIndex + 0]
                                        - rMasterBrush.m_Vertices[iVertIndex + 2]).magnitude;
                    for (int i = 0; i < 6; i++)
                    {
                        uvw   = rMasterBrush.m_UVWs[iVertIndex + i];
                        uvw.z = width;
                        rMasterBrush.m_UVWs[iVertIndex + i] = uvw;
                    }
                }
            }

            if (m_EnableBackfaces)
            {
                for (int iSolid = 0; iSolid < numSolids; ++iSolid)
                {
                    int iQuadIndex = iSegmentBack + (iSolid * quadsPerSolid);
                    int iVertIndex = iQuadIndex * 6;
                    if (m_StoreWidthInTexcoord0Z)
                    {
                        MirrorQuadFace(rMasterBrush.m_UVWs, iVertIndex);
                    }
                    else
                    {
                        MirrorQuadFace(rMasterBrush.m_UVs, iVertIndex);
                    }
                    MirrorTangents(rMasterBrush.m_Tangents, iVertIndex);
                }
            }
        }
Exemple #8
0
        /// Creates and returns a new subset containing the passed geometry.
        /// Pass:
        ///   brush - Selects the material/batch
        ///   nVerts - Amount to copy from the MasterBrush
        ///   nTris - Amount to copy from MasterBrush.m_Tris
        public BatchSubset CreateSubset(BrushDescriptor brush, int nVerts, int nTris, MasterBrush geometry)
        {
            var pool  = GetPool(brush);
            var batch = GetBatch(pool, nVerts);

            return(batch.AddSubset(nVerts, nTris, geometry));
        }
        /// Unconditionally increments m_LeadingQuadIndex by 1 or 2
        private void AppendLeadingQuad(
            bool bGenerateNew, float opacity01,
            Vector3 vCenter, Vector3 vForward, Vector3 vNormal,
            Vector3 vRight, MasterBrush rMasterBrush,
            out int earliestChangedQuad)
        {
            // Get the current stroke from the MasterBrush so that quad positions and
            // orientations can be calculated.
            Vector3[] aVerts  = rMasterBrush.m_Vertices;
            Vector3[] aNorms  = rMasterBrush.m_Normals;
            Color32[] aColors = rMasterBrush.m_Colors;

            int stride = Stride;
            // Lay leading quad
            int iVertIndex = m_LeadingQuadIndex * 6;

            PositionQuad(aVerts, iVertIndex, vCenter, vForward, vRight);
            for (int i = 0; i < 6; ++i)
            {
                aNorms[iVertIndex + i] = vNormal;
            }

            earliestChangedQuad = m_LeadingQuadIndex;

            Color32 cColor = m_Color;

            cColor.a = (byte)(opacity01 * 255.0f);
            Color32 cLastColor = (iVertIndex - stride >= 0) ? aColors[iVertIndex - stride + 4] : cColor;

            aColors[iVertIndex]     = cLastColor;
            aColors[iVertIndex + 1] = cColor;
            aColors[iVertIndex + 2] = cLastColor;
            aColors[iVertIndex + 3] = cLastColor;
            aColors[iVertIndex + 4] = cColor;
            aColors[iVertIndex + 5] = cColor;

            ++m_LeadingQuadIndex;

            // Create duplicates if we have backfaces enabled.
            if (m_EnableBackfaces)
            {
                int iCurrVertIndex = m_LeadingQuadIndex * 6;
                CreateDuplicateQuad(aVerts, aNorms, m_LeadingQuadIndex, vNormal);

                Color32 backColor, lastBackColor;
                if (m_Desc.m_BackfaceHueShift == 0)
                {
                    backColor     = cColor;
                    lastBackColor = cLastColor;
                }
                else
                {
                    HSLColor hsl = (HSLColor)(Color)m_Color;
                    hsl.HueDegrees += m_Desc.m_BackfaceHueShift;
                    backColor       = (Color32)(Color)hsl;
                    lastBackColor   = (iCurrVertIndex - stride >= 0)
          ? aColors[iCurrVertIndex - stride + 4]
          : backColor;
                }

                aColors[iCurrVertIndex]     = lastBackColor;
                aColors[iCurrVertIndex + 1] = lastBackColor;
                aColors[iCurrVertIndex + 2] = backColor;
                aColors[iCurrVertIndex + 3] = lastBackColor;
                aColors[iCurrVertIndex + 4] = backColor;
                aColors[iCurrVertIndex + 5] = backColor;

                ++m_LeadingQuadIndex;
            }

            // Walk backward and smooth out previous quads.
            int iStripLength   = m_LeadingQuadIndex;                      // In solids
            int iSegmentLength = m_LeadingQuadIndex - m_InitialQuadIndex; // In solids

            if (m_EnableBackfaces)
            {
                iStripLength   /= 2;
                iSegmentLength /= 2;
            }

            // We don't need to smooth anything if our strip is only 1 quad.
            if (iStripLength > 1)
            {
                // Indexes for later use
                int iIndexingOffset = m_EnableBackfaces ? 2 : 1;

                int iBackQuadIndex = m_LeadingQuadIndex - (3 * iIndexingOffset);
                int iBackQuadVert  = iBackQuadIndex * 6;

                int iMidQuadIndex = m_LeadingQuadIndex - (2 * iIndexingOffset);
                int iMidQuadVert  = iMidQuadIndex * 6;

                int iFrontQuadIndex = m_LeadingQuadIndex - iIndexingOffset;
                int iFrontQuadVert  = iFrontQuadIndex * 6;

                if (iSegmentLength == 1)
                {
                    // If we've got a long strip, but this segment is only 1 quad, touch up the previous quad.
                    PositionQuad(aVerts, iMidQuadVert, m_LastQuadCenter, m_LastQuadForward, m_LastQuadRight);
                    earliestChangedQuad = Mathf.Min(earliestChangedQuad, iMidQuadIndex);

                    // Fuse back to mid if it exists and if they used to be fused.
                    if (iStripLength > 2 && m_LastSegmentLengthSolids > 1)
                    {
                        FuseQuads(aVerts, aNorms, iBackQuadVert, iMidQuadVert, bGenerateNew);
                        if (m_EnableBackfaces)
                        {
                            MakeConsistentBacksideQuad(aVerts, aNorms, iBackQuadVert);
                        }
                    }
                    else if (bGenerateNew && m_LastSegmentLengthSolids == 1)
                    {
                        // If we've got a strip longer than one quad, and this segment is only one quad, it
                        // means this is the start of a new segment. If we're beginning a new segment and
                        // our previous segment is only one quad, squash that quad to clean up artifacts.
                        PositionQuad(aVerts, iMidQuadVert, m_LastQuadCenter, Vector3.zero, Vector3.zero);
                    }
                    if (m_EnableBackfaces)
                    {
                        MakeConsistentBacksideQuad(aVerts, aNorms, iMidQuadVert);
                    }
                }
                else if (iSegmentLength == 2)
                {
                    // If we've got a long strip, but this segment is only 2 quads, just fuse.
                    FuseQuads(aVerts, aNorms, iMidQuadVert, iFrontQuadVert, bGenerateNew);

                    if (m_EnableBackfaces)
                    {
                        MakeConsistentBacksideQuad(aVerts, aNorms, iMidQuadVert);
                        MakeConsistentBacksideQuad(aVerts, aNorms, iFrontQuadVert);
                    }
                }
                else
                {
                    // Set mid quad to the midpoint of back and front quads.
                    for (int i = 0; i < 6; ++i)
                    {
                        aVerts[iMidQuadVert + i] = (aVerts[iBackQuadVert + i] + aVerts[iFrontQuadVert + i]) * 0.5f;
                    }
                    // Patch up the holes by connecting the leading edge of the back quad to the trailing
                    // edge of the mid, and do the same from mid to front.
                    FuseQuads(aVerts, aNorms, iBackQuadVert, iMidQuadVert, bGenerateNew);
                    FuseQuads(aVerts, aNorms, iMidQuadVert, iFrontQuadVert, bGenerateNew);

                    if (m_EnableBackfaces)
                    {
                        MakeConsistentBacksideQuad(aVerts, aNorms, iBackQuadVert);
                        MakeConsistentBacksideQuad(aVerts, aNorms, iMidQuadVert);
                        MakeConsistentBacksideQuad(aVerts, aNorms, iFrontQuadVert);
                    }

                    // Make sure the UVs are proper
                    UpdateUVsForQuad(iMidQuadIndex);
                }
            }
        }
        /// Rewrite vertices and indices, welding together identical verts.
        /// Input topology must be as documented at the top of QuadStripBrush.cs
        /// Output topology is the same as FlatGeometryBrush
        private static void WeldSingleSidedQuadStrip(
            GeometryPool.VertexLayout layout,
            MasterBrush geometry,
            int numVerts,
            out int newNumVerts,
            out int newNumTris)
        {
            // Offsets to Front/Back Left/Right verts, QuadStrip-style (see ascii art at top of file)
            // Because of duplication, there are sometimes multiple offsets.
            // "S" is the stride, either 6 or 12 depending on usesDoubleSidedGeometry.
            // In cases where there are multiple offsets to choose from, we choose the offset
            // which makes it safe to reorder verts in-place. See the comment below re: overlaps
            const int kBrOld = 2; // also -S+5, 3
            const int kBlOld = 0; // also -S+1, -S+4
            const int kFrOld = 5; // also S+2, S+3
            const int kFlOld = 1; // also 4, S+0

            // Offsets to Front/Back Left/Right verts, FlatGeometry-style
            // See FlatGeometryBrush.cs:15
            const int kBrNew = 0;
            const int kBlNew = 1;
            const int kFrNew = 2;
            const int kFlNew = 3;

            // End result:
            //     0--1  4      keep 2, 0, 5, 1      0--1
            //     |,' ,'|  ->  ignore 3, 4      ->  |,'|
            //     2  3--5                           2--5

            int vertRead  = 0; // vertex read index
            int vertWrite = 0; // vertex write index
            int triWrite  = 0; // triangle write index

            var vs = geometry.m_Vertices;
            var ns = geometry.m_Normals;
            var cs = geometry.m_Colors;
            var ts = geometry.m_Tangents;

            var tris = geometry.m_Tris;

            var uv2s = geometry.m_UVs;
            var uv3s = geometry.m_UVWs.GetBackingArray();

            while (vertRead < numVerts)
            {
                // Compress a single connected strip.
                // The first quad is treated differently from subsequent quads,
                // because it doesn't have a previous quad to share its first two verts with.

                // First quad

                // We write to the same buffer being read from, which is a bit dangerous.
                // Correctness requires we not copy from a location that's been written to.
                // The potential problem cases where the read area overlaps the write area are:
                //   Quad #0:   write [0, 4)   read [0, 6)
                //   Quad #1:   write [4, 8)   read [6, 12)
                // For subsequent quads the read area is ahead of, and does not overlap, the write area.
                vs[vertWrite + kFlNew] = vs[vertRead + kFlOld]; // 3 <- 1   7 <- 7
                ns[vertWrite + kFlNew] = ns[vertRead + kFlOld];
                cs[vertWrite + kFlNew] = cs[vertRead + kFlOld];
                ts[vertWrite + kFlNew] = ts[vertRead + kFlOld];

                vs[vertWrite + kBlNew] = vs[vertRead + kBlOld]; // 1 <- 0   5 <- 6
                ns[vertWrite + kBlNew] = ns[vertRead + kBlOld];
                cs[vertWrite + kBlNew] = cs[vertRead + kBlOld];
                ts[vertWrite + kBlNew] = ts[vertRead + kBlOld];

                vs[vertWrite + kBrNew] = vs[vertRead + kBrOld]; // 0 <- 2   4 <- 8
                ns[vertWrite + kBrNew] = ns[vertRead + kBrOld];
                cs[vertWrite + kBrNew] = cs[vertRead + kBrOld];
                ts[vertWrite + kBrNew] = ts[vertRead + kBrOld];

                vs[vertWrite + kFrNew] = vs[vertRead + kFrOld]; // 2 <- 5   6 <- 11
                ns[vertWrite + kFrNew] = ns[vertRead + kFrOld];
                cs[vertWrite + kFrNew] = cs[vertRead + kFrOld];
                ts[vertWrite + kFrNew] = ts[vertRead + kFrOld];

                if (layout.texcoord0.size == 2)
                {
                    uv2s[vertWrite + kFlNew] = uv2s[vertRead + kFlOld];
                    uv2s[vertWrite + kBlNew] = uv2s[vertRead + kBlOld];
                    uv2s[vertWrite + kBrNew] = uv2s[vertRead + kBrOld];
                    uv2s[vertWrite + kFrNew] = uv2s[vertRead + kFrOld];
                }
                else
                {
                    uv3s[vertWrite + kFlNew] = uv3s[vertRead + kFlOld];
                    uv3s[vertWrite + kBlNew] = uv3s[vertRead + kBlOld];
                    uv3s[vertWrite + kBrNew] = uv3s[vertRead + kBrOld];
                    uv3s[vertWrite + kFrNew] = uv3s[vertRead + kFrOld];
                }

                // See FlatGeometryBrush.cs:240
                // SetTri(cur.iTri, cur.iVert, 0, BR, BL, FL);
                // SetTri(cur.iTri, cur.iVert, 1, BR, FL, FR);
                tris[triWrite + 0] = vertWrite + kBrNew;
                tris[triWrite + 1] = vertWrite + kBlNew;
                tris[triWrite + 2] = vertWrite + kFlNew;
                tris[triWrite + 3] = vertWrite + kBrNew;
                tris[triWrite + 4] = vertWrite + kFlNew;
                tris[triWrite + 5] = vertWrite + kFrNew;

                vertWrite += 4; // we wrote to a range of 4 verts
                vertRead  += 6; // we read from a range of 6 verts
                triWrite  += 6; // we wrote 6 indices

                // Remaining quads.

                // Detect strip continuation by checking a single vertex position.
                // To be fully correct, we should check both the left and right verts, and all the
                // attributes. However, as of M16 this simpler version suffices for all shipping
                // QuadStripBrushes.
                while (vertRead < numVerts && vs[vertRead + kBrOld] == vs[vertWrite - 4 + kFrNew])
                {
                    vertWrite -= 2; // Share 2 verts with the previous quad

                    // The read range will provably never overlap with the write range. Note that
                    // this cannot be quad 0 since there exists a previous quad.
                    // Therefore, the closest the ranges get is:
                    //   Quad #1:   write [2, 6)   read [6, 12)
                    vs[vertWrite + kFlNew] = vs[vertRead + kFlOld];
                    ns[vertWrite + kFlNew] = ns[vertRead + kFlOld];
                    cs[vertWrite + kFlNew] = cs[vertRead + kFlOld];
                    ts[vertWrite + kFlNew] = ts[vertRead + kFlOld];

                    vs[vertWrite + kFrNew] = vs[vertRead + kFrOld];
                    ns[vertWrite + kFrNew] = ns[vertRead + kFrOld];
                    cs[vertWrite + kFrNew] = cs[vertRead + kFrOld];
                    ts[vertWrite + kFrNew] = ts[vertRead + kFrOld];

                    if (layout.texcoord0.size == 2)
                    {
                        uv2s[vertWrite + kFlNew] = uv2s[vertRead + kFlOld];
                        uv2s[vertWrite + kFrNew] = uv2s[vertRead + kFrOld];
                    }
                    else
                    {
                        uv3s[vertWrite + kFlNew] = uv3s[vertRead + kFlOld];
                        uv3s[vertWrite + kFrNew] = uv3s[vertRead + kFrOld];
                    }

                    tris[triWrite + 0] = vertWrite + kBrNew;
                    tris[triWrite + 1] = vertWrite + kBlNew;
                    tris[triWrite + 2] = vertWrite + kFlNew;
                    tris[triWrite + 3] = vertWrite + kBrNew;
                    tris[triWrite + 4] = vertWrite + kFlNew;
                    tris[triWrite + 5] = vertWrite + kFrNew;

                    vertWrite += 4;
                    vertRead  += 6;
                    triWrite  += 6;
                }
            }

            newNumVerts = vertWrite;
            newNumTris  = triWrite;
        }