/********************************************************************** Filename : GEdgeAA.cpp Content : Created : 2005-2006 Authors : Maxim Shemanarev Copyright : (c) 2005-2006 Scaleform Corp. All Rights Reserved. Patent Pending. Contact Scaleform for more information. Notes : Licensees may use this file in accordance with the valid Scaleform Commercial License Agreement provided with the software. This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR ANY PURPOSE. **********************************************************************/ #include "GEdgeAA.h" #include // remove! #ifndef GFC_NO_FXPLAYER_EDGEAA const unsigned GEdgeAA::VertexIdx[6] = {0, 1, 2, 0, 1, 2 }; //------------------------------------------------------------------------ UPInt GEdgeAA::GetNumBytes() const { return MeshTriangles.GetNumBytes() + EdgeIdx.GetNumBytes() + FanEdges.GetNumBytes() + TmpStarVer.GetNumBytes() + Triangles.GetNumBytes() + VertexMap.GetNumBytes(); } //------------------------------------------------------------------------ void GEdgeAA::Clear() { Vertices.Clear(); Edges.Clear(); MeshTriangles.Clear(); } //------------------------------------------------------------------------ void GEdgeAA::ClearAndRelease() { Vertices.ClearAndRelease(); Edges.ClearAndRelease(); MeshTriangles.ClearAndRelease(); EdgeIdx.ClearAndRelease(); FanEdges.ClearAndRelease(); TmpStarVer.ClearAndRelease(); Triangles.ClearAndRelease(); VertexMap.ClearAndRelease(); } //------------------------------------------------------------------------ void GEdgeAA::AddVertex(const GPointType& v) { VertexType tv; tv.x = v.x; tv.y = v.y; tv.id = TransparencyBit | StyleMask; Vertices.PushBack(tv); } //------------------------------------------------------------------------ void GEdgeAA::AddTriangle(unsigned v1, unsigned v2, unsigned v3, unsigned style) { EdgeType e; MeshTriType tri; tri.iniVer[0] = v1; tri.iniVer[1] = v2; tri.iniVer[2] = v3; tri.newVer[0] = v1; tri.newVer[1] = v2; tri.newVer[2] = v3; tri.startEdge = (unsigned)Edges.GetSize(); tri.adjTri[0] = tri.adjTri[1] = tri.adjTri[2] = -1; tri.extVer[0] = tri.extVer[1] = tri.extVer[2] = -1; tri.edgeStat[0] = tri.edgeStat[1] = tri.edgeStat[2] = 0; tri.style = style; e.tri = (unsigned)MeshTriangles.GetSize() << 2; e.v1 = v1; e.v2 = v2; Edges.PushBack(e); e.tri++; e.v1 = v2; e.v2 = v3; Edges.PushBack(e); e.tri++; e.v1 = v3; e.v2 = v1; Edges.PushBack(e); MeshTriangles.PushBack(tri); DefaultStyle = style; } //------------------------------------------------------------------------ void GEdgeAA::buildAdjacencyTable() { EdgeIdx.Resize(Edges.GetSize()); UPInt i; for(i = 0; i < Edges.GetSize(); i++) { EdgeIdx[i] = (unsigned)i; } EdgeIdxLess edgeLess(Vertices, Edges); G_QuickSort(EdgeIdx, edgeLess); for(i = 0; i < MeshTriangles.GetSize(); i++) { MeshTriType& tri = MeshTriangles[i]; tri.adjTri[0] = findAdjacentTriangle(tri.startEdge); tri.adjTri[1] = findAdjacentTriangle(tri.startEdge + 1); tri.adjTri[2] = findAdjacentTriangle(tri.startEdge + 2); } } //------------------------------------------------------------------------ bool GEdgeAA::buildEdgesFan(unsigned triIdx) { int adjStart = triIdx; int adj = adjStart; int adj1, adj2; bool cyclic = false; int lastEdge = 0x7FFFFFFF; unsigned lastIdx = 0; FanEdges.Clear(); for(;;) { FanEdges.PushBack(adj1 = adj); FanEdges.PushBack(adj2 = adj = triangleNextIdx(adj, 2)); if(adj1 == lastEdge || adj2 == lastEdge) { // Protection from infinite loops in degenerate cases FanEdges.Clear(); return true; } adj = adjacentTriangle(adj); if(adj == adjStart) { cyclic = true; break; } if(adj < 0) { break; } lastEdge = FanEdges[lastIdx++]; } G_ReverseArray(FanEdges); if(cyclic) { return true; } adj = adjStart; for(;;) { adj = adjacentTriangle(adj); if(adj < 0) { break; } FanEdges.PushBack(adj1 = adj); FanEdges.PushBack(adj2 = adj = triangleNextIdx(adj, 1)); if(adj1 == lastEdge || adj2 == lastEdge) { // Protection from infinite loops in degenerate cases FanEdges.Clear(); return true; } lastEdge = FanEdges[lastIdx++]; } return false; } //------------------------------------------------------------------------ void GEdgeAA::calcIntersectionPoint(GCoordType width, GCoordType lim, unsigned start, unsigned end, GCoordType* x, GCoordType* y) const { VertexType v1 = triangleVertex(FanEdges[start], 0); VertexType v2 = triangleVertex(FanEdges[start], 1); VertexType v3 = triangleVertex(FanEdges[end], 0); VertexType v4 = triangleVertex(FanEdges[end], 1); GCoordType vx = v2.x; GCoordType vy = v2.y; GCoordType len1 = GMath2D::CalcDistance(v1, v2); GCoordType len2 = GMath2D::CalcDistance(v3, v4); GCoordType epsilon = (len1 + len2) * G_IntersectionEpsilonAA; GCoordType dx1 = width * (v2.y - v1.y) / len1; GCoordType dy1 = width * (v1.x - v2.x) / len1; GCoordType dx2 = width * (v4.y - v3.y) / len2; GCoordType dy2 = width * (v3.x - v4.x) / len2; if(GMath2D::CalcIntersection(v1.x + dx1, v1.y + dy1, v2.x + dx1, v2.y + dy1, v3.x + dx2, v3.y + dy2, v4.x + dx2, v4.y + dy2, x, y, epsilon)) { GCoordType dist = GMath2D::CalcDistance(*x, *y, vx, vy); if (len1 < len2) len1 = len2; if (lim > len1) lim = len1; if(dist > lim) { GCoordType k = lim / dist; *x = vx + (*x - vx) * k; *y = vy + (*y - vy) * k; } } else { if (len1 > len2) { *x = v2.x + dx1; *y = v2.y + dy1; } else { *x = v3.x + dx2; *y = v3.y + dy2; } } } //------------------------------------------------------------------------ void GEdgeAA::correctCrossIntersection(const VertexType& iniVer, const MeshTriType& tri, unsigned triVerIdx, VertexType* newVer, GCoordType width) const { GUNUSED(width); const VertexType& v1 = Vertices[tri.newVer[VertexIdx[triVerIdx + 1]]]; const VertexType& v2 = Vertices[tri.newVer[VertexIdx[triVerIdx + 2]]]; if(GMath2D::CrossProduct(*newVer, v1, v2) < -0.1) { return; } // A simple variant, works fairly well GCoordType x, y; GCoordType epsilon = (fabsf(iniVer.x - newVer->x) + fabsf(iniVer.y - newVer->y) + fabsf(v1.x - v2.x) + fabsf(v1.y - v2.y)) * G_IntersectionEpsilonAA; if(GMath2D::CalcIntersection(iniVer, *newVer, v1, v2, &x, &y, epsilon)) { x += (iniVer.x - x) * (GCoordType)0.25; // Correction to reduce possible overlaps y += (iniVer.y - y) * (GCoordType)0.25; newVer->x = x; newVer->y = y; } //// An attempt to improve it. Doesn't help much //GCoordType cp1 = GMath2D::CrossProduct(iniVer, *newVer, v1); //GCoordType cp2 = GMath2D::CrossProduct(iniVer, *newVer, v2); //GCoordType x, y; // //if(cp1 > 0 && cp2 < 0) //{ // // The new vector spits the triangle // if(GMath2D::CalcIntersection(iniVer, *newVer, // v1, v2, // &x, &y, // G_IntersectionEpsilonAA)) // { // newVer->x = x; // newVer->y = y; // } //} //else //{ // // The vector is outside of the triangle // GCoordType d; // GCoordType k = 1; // // if(cp1 <= 0) // { // d = GMath2D::CalcDistance(iniVer, v1); // if(width < d) // { // k = width / d; // } // newVer->x = iniVer.x + (v1.x - iniVer.x) * k; // newVer->y = iniVer.y + (v1.y - iniVer.y) * k; // } // else // if(cp2 >= 0) // { // d = GMath2D::CalcDistance(iniVer, v2); // if(width < d) // { // k = width / d; // } // newVer->x = iniVer.x + (v2.x - iniVer.x) * k; // newVer->y = iniVer.y + (v2.y - iniVer.y) * k; // } //} } //------------------------------------------------------------------------ int GEdgeAA::findAdjacentTriangle(unsigned ei) const { EdgeType e; e.v1 = Edges[ei].v2; e.v2 = Edges[ei].v1; EdgeLess edgeLess(Vertices, Edges); UPInt pos = G_LowerBound(EdgeIdx, e, edgeLess); if(pos < EdgeIdx.GetSize()) { const EdgeType& e2 = Edges[EdgeIdx[pos]]; if(e2.v1 == e.v1 && e2.v2 == e.v2) { return e2.tri; } } return -1; } //------------------------------------------------------------------------ int GEdgeAA::findSameVertex(int id, GCoordType x, GCoordType y) const { unsigned i; for (i = (unsigned)Vertices.GetSize(); i > StartDuplicates; --i) { const VertexType& v2 = Vertices[i - 1]; if (unsigned(v2.id & StyleMask) == unsigned(id) && v2.x == x && v2.y == y) return int(i - 1); } return -1; } //------------------------------------------------------------------------ unsigned GEdgeAA::assignStyle(unsigned v1, unsigned v2, unsigned v3) { int id = Vertices[v1].id; if (id & TransparencyBit) { int id2 = Vertices[v2].id; int id3 = Vertices[v3].id; int srcId; if (id2 & TransparencyBit) srcId = id3 & StyleMask; else srcId = id2 & StyleMask; if (unsigned(id & StyleMask) != unsigned(srcId)) { if(id & NeedsDuplicate) { VertexType newVer = Vertices[v1]; int newIdx = findSameVertex(srcId, newVer.x, newVer.y); if (newIdx >= 0) { v1 = newIdx; } else { newVer.id = (id & FlagsMask) | srcId | VertexUsed; v1 = (unsigned)Vertices.GetSize(); Vertices.PushBack(newVer); } } else { Vertices[v1].id = (id & FlagsMask) | srcId | NeedsDuplicate | VertexUsed; } } } return v1; } //------------------------------------------------------------------------ void GEdgeAA::ProcessEdges(GCoordType width, AA_Method aaMethod) { unsigned i, j, k; Triangles.Clear(); buildAdjacencyTable(); if(width >= 0) { return; } int defaultStyle = TransparencyBit | VertexUsed | StyleMask; if(aaMethod == AA_OuterEdges) { width *= 2; defaultStyle = TransparencyBit | VertexUsed | DefaultStyle; } GCoordType lim = fabsf(width) * GetIntersectionMiterLimit(); for(i = 0; i < MeshTriangles.GetSize(); i++) { const MeshTriType& tri = MeshTriangles[i]; for(j = 0; j < 3; j++) { if((tri.edgeStat[j] & EdgeModified) == 0 && (Vertices[tri.iniVer[j]].id & VertexProcessed) == 0) { bool cyclic = buildEdgesFan((i << 2) + j); if(FanEdges.GetSize() == 0) { continue; } if(cyclic && MeshTriangles[FanEdges[0] >> 2].style == MeshTriangles[FanEdges.Back() >> 2].style) { // Process only those cases that have different // styles at start and the end. // It protects us from using improper starting edges, // as well as from processing of cyclic fans with // all the same styles. continue; } GCoordType x, y; unsigned newVerIdx; unsigned adjVal; TmpStarVer.Clear(); const VertexType& center = Vertices[tri.iniVer[j]]; if(!cyclic) { // Non-circular fan means that we have external edges calcIntersectionPoint(-width, lim, 0, (unsigned)FanEdges.GetSize() - 1, &x, &y); newVerIdx = (unsigned)Vertices.GetSize(); Vertices.PushBack(VertexType(x, y, defaultStyle)); TmpStarVer.PushBack(newVerIdx); adjVal = FanEdges[0]; MeshTriangles[adjVal >> 2].extVer[VertexIdx[(adjVal & 3) + 1]] = newVerIdx; adjVal = FanEdges.Back(); MeshTriangles[adjVal >> 2].extVer[adjVal & 3] = newVerIdx; } if(aaMethod == AA_AllEdges) { unsigned start, end; for(start = 0; start < FanEdges.GetSize(); ) { const MeshTriType& t1 = MeshTriangles[FanEdges[start] >> 2]; for(end = start + 1; end < FanEdges.GetSize(); end++) { if(t1.style != MeshTriangles[FanEdges[end] >> 2].style) { break; } } calcIntersectionPoint(width, lim, start, end - 1, &x, &y); newVerIdx = (unsigned)Vertices.GetSize(); Vertices.PushBack(VertexType(x, y, defaultStyle)); TmpStarVer.PushBack(newVerIdx); VertexType& newVer = Vertices.Back(); for(k = start; k < end; k += 2) { unsigned triVerIdx = triangleNextIdx(FanEdges[k], 1); MeshTriType& t2 = MeshTriangles[triVerIdx >> 2]; correctCrossIntersection(Vertices[tri.iniVer[j]], t2, triVerIdx & 3, &newVer, -width); t2.newVer[triVerIdx & 3] = newVerIdx; t2.edgeStat[triVerIdx & 3] |= EdgeModified; } start = end; } while(TmpStarVer.GetSize() > 2) { GCoordType maxDist = 0; unsigned maxIdx = 0; unsigned s = (unsigned)TmpStarVer.GetSize(); for(k = 0; k < s; k++) { GCoordType d = GMath2D::CalcDistance(Vertices[TmpStarVer[k]], center); if(d > maxDist) { maxDist = d; maxIdx = k; } } addTriangle(TmpStarVer[(maxIdx + s - 1) % s], TmpStarVer[ maxIdx ], TmpStarVer[(maxIdx + 1) % s ]); TmpStarVer.RemoveAt(maxIdx); } } Vertices[tri.iniVer[j]].id |= VertexProcessed; } } } for(i = 0; i < MeshTriangles.GetSize(); i++) { MeshTriType& t1 = MeshTriangles[i]; if(t1.newVer[0] != t1.newVer[1] && t1.newVer[1] != t1.newVer[2] && t1.newVer[2] != t1.newVer[0]) { addTriangle(t1.newVer[0], t1.newVer[1], t1.newVer[2]); } for(j = 0; j < 3; j++) { unsigned idx = t1.newVer[j]; Vertices[idx].id = t1.style | VertexUsed; if((t1.edgeStat[j] & TrapezoidEmitted) == 0) { int adj = t1.adjTri[j]; int v1, v2, v3, v4; if(adj < 0) { // External edge v3 = t1.extVer[VertexIdx[j + 1]]; v4 = t1.extVer[j]; if(v3 >= 0 && v4 >= 0) { v1 = triangleNewVerIdx((i << 2) | j); v2 = triangleNewVerIdx((i << 2) | j, 1); addTriangle(v1, v2, v4); addTriangle(v4, v2, v3); } } else { // Internal edge; MeshTriType& t2 = MeshTriangles[adj >> 2]; if(t2.style != t1.style) { v1 = triangleNewVerIdx((i << 2) | j); v2 = triangleNewVerIdx((i << 2) | j, 1); v3 = triangleNewVerIdx(adj); v4 = triangleNewVerIdx(adj, 1); addTriangle(v1, v2, v4); addTriangle(v4, v2, v3); t2.edgeStat[adj & 3] |= TrapezoidEmitted; } } t1.edgeStat[j] |= TrapezoidEmitted; } } } if(aaMethod == AA_AllEdges) { VertexMap.Resize(Vertices.GetSize()); for(i = j = 0; i < (unsigned)Vertices.GetSize(); ++i) { VertexMap[i] = j; if (Vertices[i].id & VertexUsed) { Vertices[j++] = Vertices[i]; } } Vertices.CutAt(StartDuplicates = j); for(i = 0; i < (unsigned)Triangles.GetSize(); ++i) { TriangleType t1 = Triangles[i]; TriangleType t2; t1.v1 = VertexMap[t1.v1]; t1.v2 = VertexMap[t1.v2]; t1.v3 = VertexMap[t1.v3]; t2.v1 = assignStyle(t1.v1, t1.v2, t1.v3); t2.v2 = assignStyle(t1.v2, t1.v3, t1.v1); t2.v3 = assignStyle(t1.v3, t1.v1, t1.v2); Triangles[i] = t2; } } } //------------------------------------------------------------------------ void GEdgeAA::SortTrianglesByStyle() { unsigned i; // Arrange styles in triangles for(i = 0; i < Triangles.GetSize(); i++) { TriangleType& tri = Triangles[i]; if(Vertices[tri.v2].id < Vertices[tri.v1].id) G_Swap(tri.v2, tri.v1); if(Vertices[tri.v3].id < Vertices[tri.v2].id) G_Swap(tri.v3, tri.v2); if(Vertices[tri.v2].id < Vertices[tri.v1].id) G_Swap(tri.v2, tri.v1); } // Arrange triangles by styles in lexicographical order TriangleLess triangleLess(Vertices); G_QuickSort(Triangles, triangleLess); } #endif // #ifndef GFC_NO_FXPLAYER_EDGEAA