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(); }
/// 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); } } }
/// 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; }