static ClipCounts UpperBoundApproximateRectClippingResults(Vertex[] vertices, UInt16[] indices, Vector4 clipRectMinMax) { ClipCounts cc = new ClipCounts(); cc.firstClippedIndex = int.MaxValue; cc.firstDegenerateIndex = -1; cc.lastClippedIndex = -1; int indexCount = indices.Length; for (int i = 0; i < indexCount; i += 3) { Vector3 v0 = vertices[indices[i]].position; Vector3 v1 = vertices[indices[i + 1]].position; Vector3 v2 = vertices[indices[i + 2]].position; Vector4 triRectMinMax; triRectMinMax.x = v0.x < v1.x ? v0.x : v1.x; triRectMinMax.x = triRectMinMax.x < v2.x ? triRectMinMax.x : v2.x; triRectMinMax.y = v0.y < v1.y ? v0.y : v1.y; triRectMinMax.y = triRectMinMax.y < v2.y ? triRectMinMax.y : v2.y; triRectMinMax.z = v0.x > v1.x ? v0.x : v1.x; triRectMinMax.z = triRectMinMax.z > v2.x ? triRectMinMax.z : v2.x; triRectMinMax.w = v0.y > v1.y ? v0.y : v1.y; triRectMinMax.w = triRectMinMax.w > v2.y ? triRectMinMax.w : v2.y; // Test if the rect is outside (degenerate triangle), or totally inside (not clipped), // else it is _probably_ clipped, but can still be either degenerate or unclipped if ((triRectMinMax.x >= clipRectMinMax.x) && (triRectMinMax.z <= clipRectMinMax.z) && (triRectMinMax.y >= clipRectMinMax.y) && (triRectMinMax.w <= clipRectMinMax.w)) { cc.firstDegenerateIndex = -1; continue; // Not clipped } cc.firstClippedIndex = cc.firstClippedIndex < i ? cc.firstClippedIndex : i; cc.lastClippedIndex = i + 2; if ((triRectMinMax.x >= clipRectMinMax.z) || (triRectMinMax.z <= clipRectMinMax.x) || (triRectMinMax.y >= clipRectMinMax.w) || (triRectMinMax.w <= clipRectMinMax.y)) { cc.firstDegenerateIndex = cc.firstDegenerateIndex == -1 ? i : cc.firstDegenerateIndex; cc.degenerateTriangles++; } cc.firstDegenerateIndex = -1; cc.clippedTriangles++; cc.addedTriangles += 4; // Triangles clipping against corners may spawn more triangles } return(cc); }
unsafe static void RectClip(Vertex[] vertices, UInt16[] indices, Vector4 clipRectMinMax, MeshWriteData mwd, ClipCounts cc, ref int newVertexCount) { int lastEffectiveClippedIndex = cc.lastClippedIndex; if (cc.firstDegenerateIndex != -1 && cc.firstDegenerateIndex < lastEffectiveClippedIndex) { lastEffectiveClippedIndex = cc.firstDegenerateIndex; } UInt16 nextNewVertex = (UInt16)vertices.Length; // Copy all non-clipped indices for (int i = 0; i < cc.firstClippedIndex; i++) { mwd.SetNextIndex(indices[i]); } // Clipped triangles UInt16 *it = stackalloc UInt16[3]; // Indices of the triangle Vertex *vt = stackalloc Vertex[3]; // Vertices of the triangle for (int i = cc.firstClippedIndex; i < lastEffectiveClippedIndex; i += 3) { it[0] = indices[i]; it[1] = indices[i + 1]; it[2] = indices[i + 2]; vt[0] = vertices[it[0]]; vt[1] = vertices[it[1]]; vt[2] = vertices[it[2]]; Vector4 triRectMinMax; triRectMinMax.x = vt[0].position.x < vt[1].position.x ? vt[0].position.x : vt[1].position.x; triRectMinMax.x = triRectMinMax.x < vt[2].position.x ? triRectMinMax.x : vt[2].position.x; triRectMinMax.y = vt[0].position.y < vt[1].position.y ? vt[0].position.y : vt[1].position.y; triRectMinMax.y = triRectMinMax.y < vt[2].position.y ? triRectMinMax.y : vt[2].position.y; triRectMinMax.z = vt[0].position.x > vt[1].position.x ? vt[0].position.x : vt[1].position.x; triRectMinMax.z = triRectMinMax.z > vt[2].position.x ? triRectMinMax.z : vt[2].position.x; triRectMinMax.w = vt[0].position.y > vt[1].position.y ? vt[0].position.y : vt[1].position.y; triRectMinMax.w = triRectMinMax.w > vt[2].position.y ? triRectMinMax.w : vt[2].position.y; // Test if the rect is outside (degenerate triangle), or totally inside (not clipped), // else it is _probably_ clipped, but can still be either degenerate or unclipped if ((triRectMinMax.x >= clipRectMinMax.x) && (triRectMinMax.z <= clipRectMinMax.z) && (triRectMinMax.y >= clipRectMinMax.y) && (triRectMinMax.w <= clipRectMinMax.w)) { // Clean triangle mwd.SetNextIndex(it[0]); mwd.SetNextIndex(it[1]); mwd.SetNextIndex(it[2]); continue; } if ((triRectMinMax.x >= clipRectMinMax.z) || (triRectMinMax.z <= clipRectMinMax.x) || (triRectMinMax.y >= clipRectMinMax.w) || (triRectMinMax.w <= clipRectMinMax.y)) { continue; // Skip this triangle. It is fully clipped. } // The full shabang RectClipTriangle(vt, it, clipRectMinMax, mwd, ref nextNewVertex); } // Copy remaining non-clipped indices int indexCount = indices.Length; for (int i = cc.lastClippedIndex + 1; i < indexCount; i++) { mwd.SetNextIndex(indices[i]); } newVertexCount = nextNewVertex; mwd.m_Vertices = mwd.m_Vertices.Slice(0, newVertexCount); mwd.m_Indices = mwd.m_Indices.Slice(0, mwd.currentIndex); }
internal static void MakeVectorGraphicsStretchBackground(Vertex[] svgVertices, UInt16[] svgIndices, float svgWidth, float svgHeight, Rect targetRect, Rect sourceUV, ScaleMode scaleMode, Color tint, int settingIndexOffset, AllocMeshData meshAlloc, out int finalVertexCount, out int finalIndexCount) { // Determine position offset and scale according to scale mode Vector2 svgSubRectSize = new Vector2(svgWidth * sourceUV.width, svgHeight * sourceUV.height); Vector2 svgSubRectOffset = new Vector2(sourceUV.xMin * svgWidth, sourceUV.yMin * svgHeight); Rect svgSubRect = new Rect(svgSubRectOffset, svgSubRectSize); bool isSubRect = sourceUV.xMin != 0 || sourceUV.yMin != 0 || sourceUV.width != 1 || sourceUV.height != 1; float srcAspect = svgSubRectSize.x / svgSubRectSize.y; float destAspect = targetRect.width / targetRect.height; Vector2 posOffset, posScale; switch (scaleMode) { case ScaleMode.StretchToFill: posOffset = new Vector2(0, 0); posScale.x = targetRect.width / svgSubRectSize.x; posScale.y = targetRect.height / svgSubRectSize.y; break; case ScaleMode.ScaleAndCrop: // ScaleAndCrop keeps the content centered to follow the same behavior as textures posOffset = new Vector2(0, 0); if (destAspect > srcAspect) { // Fill on x and crop top/bottom sides posScale.x = posScale.y = targetRect.width / svgSubRectSize.x; var height = targetRect.height / posScale.y; float offset = svgSubRect.height / 2.0f - height / 2.0f; posOffset.y -= offset * posScale.y; svgSubRect.y += offset; svgSubRect.height = height; isSubRect = true; } else if (destAspect < srcAspect) { // Fill on y and crop left/right sides posScale.x = posScale.y = targetRect.height / svgSubRectSize.y; var width = targetRect.width / posScale.x; float offset = svgSubRect.width / 2.0f - width / 2.0f; posOffset.x -= offset * posScale.x; svgSubRect.x += offset; svgSubRect.width = width; isSubRect = true; } else { posScale.x = posScale.y = targetRect.width / svgSubRectSize.x; // Just scale, cropping is not involved } break; case ScaleMode.ScaleToFit: if (destAspect > srcAspect) { // Fill on y and offset on x posScale.x = posScale.y = targetRect.height / svgSubRectSize.y; posOffset.x = (targetRect.width - svgSubRectSize.x * posScale.x) * 0.5f; posOffset.y = 0; } else { // Fill on x and offset on y posScale.x = posScale.y = targetRect.width / svgSubRectSize.x; posOffset.x = 0; posOffset.y = (targetRect.height - svgSubRectSize.y * posScale.y) * 0.5f; } break; default: throw new NotImplementedException(); } s_VectorGraphicsStretch.Begin(); posOffset -= svgSubRectOffset * posScale; int newVertexCount = svgVertices.Length; int newIndexCount = svgIndices.Length; ClipCounts cc = new ClipCounts(); Vector4 svgSubRectMinMax = Vector4.zero; if (isSubRect) { if (svgSubRect.width <= 0 || svgSubRect.height <= 0) { finalVertexCount = finalIndexCount = 0; s_VectorGraphicsStretch.End(); return; // Totally clipped } svgSubRectMinMax = new Vector4(svgSubRect.xMin, svgSubRect.yMin, svgSubRect.xMax, svgSubRect.yMax); cc = UpperBoundApproximateRectClippingResults(svgVertices, svgIndices, svgSubRectMinMax); // We never kill vertices, just triangles.. so the clipper will only cause growth in the vertex count newVertexCount += cc.clippedTriangles * 6; // 6 new vertices per clipped triangle newIndexCount += cc.addedTriangles * 3; newIndexCount -= cc.degenerateTriangles * 3; // We will not add the indices of degenerate triangles, so discount them } var mwd = meshAlloc.alloc((uint)newVertexCount, (uint)newIndexCount, ref meshAlloc); // Copy indices straight. If clipping is involved, perform clipping. This will fill all the indices // as well as register some new vertices at the end of the original set of vertices if (isSubRect) { RectClip(svgVertices, svgIndices, svgSubRectMinMax, mwd, cc, ref newVertexCount); } else { mwd.SetAllIndices(svgIndices); } // Transform all original vertices, vertices generated by clipping will use those directly var uvRegion = mwd.uvRegion; int vertsCount = svgVertices.Length; for (int i = 0; i < vertsCount; i++) { var v = svgVertices[i]; v.position.x = v.position.x * posScale.x + posOffset.x; v.position.y = v.position.y * posScale.y + posOffset.y; v.uv.x = v.uv.x * uvRegion.width + uvRegion.xMin; v.uv.y = v.uv.y * uvRegion.height + uvRegion.yMin; v.tint *= tint; uint settingIndex = (uint)(((v.opacityPageSVGSettingIndex.b << 8) | v.opacityPageSVGSettingIndex.a) + settingIndexOffset); v.opacityPageSVGSettingIndex.b = (byte)(settingIndex >> 8); v.opacityPageSVGSettingIndex.a = (byte)settingIndex; mwd.SetNextVertex(v); } // Transform newely generated vertices as well (if any) for (int i = vertsCount; i < newVertexCount; i++) { var v = mwd.m_Vertices[i]; v.position.x = v.position.x * posScale.x + posOffset.x; v.position.y = v.position.y * posScale.y + posOffset.y; v.uv.x = v.uv.x * uvRegion.width + uvRegion.xMin; v.uv.y = v.uv.y * uvRegion.height + uvRegion.yMin; v.tint *= tint; uint settingIndex = (uint)(((v.opacityPageSVGSettingIndex.b << 8) | v.opacityPageSVGSettingIndex.a) + settingIndexOffset); v.opacityPageSVGSettingIndex.b = (byte)(settingIndex >> 8); v.opacityPageSVGSettingIndex.a = (byte)settingIndex; mwd.m_Vertices[i] = v; } finalVertexCount = mwd.vertexCount; finalIndexCount = mwd.indexCount; s_VectorGraphicsStretch.End(); }