unit KM_RenderAux_Mobile; {$I KaM_Remake.inc} interface uses KM_Points, KM_CommonTypes; procedure KMRenderAuxMobileFill( const aPoints: array of TKMPointF; const aColor: TKMColor4f); procedure KMRenderAuxMobileStroke( const aPoints: array of TKMPointF; aClosed: Boolean; aWorldWidth: Single; aPattern: Word; aPatternBitWorldLength: Single; const aColor: TKMColor4f); implementation uses SysUtils, Math, KM_Render; type TSolidVertexArray = array of TKMSolidVertex; TPointArray = array of TKMPointF; TIndexArray = array of Integer; function IsFinitePoint(const P: TKMPointF): Boolean; begin Result := not IsNan(P.X) and not IsInfinite(P.X) and not IsNan(P.Y) and not IsInfinite(P.Y); end; function IsFiniteColor(const C: TKMColor4f): Boolean; begin Result := not IsNan(C.R) and not IsInfinite(C.R) and not IsNan(C.G) and not IsInfinite(C.G) and not IsNan(C.B) and not IsInfinite(C.B) and not IsNan(C.A) and not IsInfinite(C.A); end; function SignedCross(const A, B, C: TKMPointF): Double; begin Result := (Double(B.X) - A.X) * (Double(C.Y) - A.Y) - (Double(B.Y) - A.Y) * (Double(C.X) - A.X); end; function SameAdjacentPoint(const A, B: TKMPointF): Boolean; begin Result := (A.X = B.X) and (A.Y = B.Y); end; function Distance(const A, B: TKMPointF): Double; var DX, DY: Double; begin DX := Double(A.X) - B.X; DY := Double(A.Y) - B.Y; Result := Sqrt(DX * DX + DY * DY); end; function NearClosingRepeat(const Points: TPointArray): Boolean; var FirstEdge, LastEdge, ClosingDistance: Double; I: Integer; begin Result := False; if Length(Points) < 3 then Exit; if SameAdjacentPoint(Points[0], Points[High(Points)]) then begin Result := True; Exit; end; FirstEdge := 0; for I := 1 to High(Points) do begin FirstEdge := Distance(Points[I - 1], Points[I]); if FirstEdge > 0 then Break; end; LastEdge := 0; for I := High(Points) - 1 downto 0 do begin LastEdge := Distance(Points[I + 1], Points[I]); if LastEdge > 0 then Break; end; if (FirstEdge <= 0) or (LastEdge <= 0) then Exit; ClosingDistance := Distance(Points[0], Points[High(Points)]); Result := ClosingDistance <= 1E-6 * Min(FirstEdge, LastEdge); end; function OnSegment(const A, B, P: TKMPointF): Boolean; begin Result := (SignedCross(A, B, P) = 0) and (P.X >= Min(A.X, B.X)) and (P.X <= Max(A.X, B.X)) and (P.Y >= Min(A.Y, B.Y)) and (P.Y <= Max(A.Y, B.Y)); end; function SegmentsIntersect(const A, B, C, D: TKMPointF): Boolean; var O1, O2, O3, O4: Double; begin O1 := SignedCross(A, B, C); O2 := SignedCross(A, B, D); O3 := SignedCross(C, D, A); O4 := SignedCross(C, D, B); Result := ((O1 = 0) and OnSegment(A, B, C)) or ((O2 = 0) and OnSegment(A, B, D)) or ((O3 = 0) and OnSegment(C, D, A)) or ((O4 = 0) and OnSegment(C, D, B)) or ((((O1 < 0) and (O2 > 0)) or ((O1 > 0) and (O2 < 0))) and (((O3 < 0) and (O4 > 0)) or ((O3 > 0) and (O4 < 0)))); end; function SegmentCrossesOrTouchesNonNeighbor(const Points: TPointArray; EdgeA, EdgeB: Integer): Boolean; var N, NextA, NextB: Integer; Shared: Boolean; CommonPoint, OtherA, OtherB: TKMPointF; begin N := Length(Points); NextA := (EdgeA + 1) mod N; NextB := (EdgeB + 1) mod N; Shared := (EdgeA = EdgeB) or (NextA = EdgeB) or (NextB = EdgeA); if not SegmentsIntersect(Points[EdgeA], Points[NextA], Points[EdgeB], Points[NextB]) then begin Result := False; Exit; end; if not Shared then begin Result := True; Exit; end; if NextA = EdgeB then begin CommonPoint := Points[NextA]; OtherA := Points[EdgeA]; OtherB := Points[NextB]; end else if NextB = EdgeA then begin CommonPoint := Points[NextB]; OtherA := Points[NextA]; OtherB := Points[EdgeB]; end else begin Result := True; Exit; end; Result := (SignedCross(CommonPoint, OtherA, OtherB) = 0) and ((Double(OtherA.X) - CommonPoint.X) * (Double(OtherB.X) - CommonPoint.X) + (Double(OtherA.Y) - CommonPoint.Y) * (Double(OtherB.Y) - CommonPoint.Y) > 0); end; function PointInOrOnTriangle(const P, A, B, C: TKMPointF; Winding: Double): Boolean; begin Result := (Winding * SignedCross(A, B, P) >= 0) and (Winding * SignedCross(B, C, P) >= 0) and (Winding * SignedCross(C, A, P) >= 0); end; procedure AppendTriangle(const A, B, C: TKMPointF; var Dest: TSolidVertexArray); var L: Integer; begin L := Length(Dest); SetLength(Dest, L + 3); Dest[L].X := A.X; Dest[L].Y := A.Y; Dest[L].Z := 0.0; Dest[L + 1].X := B.X; Dest[L + 1].Y := B.Y; Dest[L + 1].Z := 0.0; Dest[L + 2].X := C.X; Dest[L + 2].Y := C.Y; Dest[L + 2].Z := 0.0; end; procedure AppendRibbon(const A, B: TKMPointF; HalfWidth: Single; var Triangles: TSolidVertexArray); var DX, DY, L, NX, NY: Double; AL, AR, BL, BR: TKMPointF; begin DX := Double(B.X) - A.X; DY := Double(B.Y) - A.Y; L := Sqrt(DX * DX + DY * DY); if L = 0 then Exit; NX := -DY / L * HalfWidth; NY := DX / L * HalfWidth; AL.X := A.X + NX; AL.Y := A.Y + NY; AR.X := A.X - NX; AR.Y := A.Y - NY; BL.X := B.X + NX; BL.Y := B.Y + NY; BR.X := B.X - NX; BR.Y := B.Y - NY; AppendTriangle(AL, BL, BR, Triangles); AppendTriangle(AL, BR, AR, Triangles); end; procedure AppendBevel(const Prev, P, Next: TKMPointF; HalfWidth: Single; var Triangles: TSolidVertexArray); var DX1, DY1, DX2, DY2, L1, L2, CrossValue: Double; NX1, NY1, NX2, NY2: Double; V1, V2: TKMPointF; begin DX1 := Double(P.X) - Prev.X; DY1 := Double(P.Y) - Prev.Y; DX2 := Double(Next.X) - P.X; DY2 := Double(Next.Y) - P.Y; L1 := Sqrt(DX1 * DX1 + DY1 * DY1); L2 := Sqrt(DX2 * DX2 + DY2 * DY2); if (L1 = 0) or (L2 = 0) then Exit; DX1 := DX1 / L1; DY1 := DY1 / L1; DX2 := DX2 / L2; DY2 := DY2 / L2; CrossValue := DX1 * DY2 - DY1 * DX2; if (Abs(CrossValue) <= 1E-6) or ((DX1 + DX2) * (DX1 + DX2) + (DY1 + DY2) * (DY1 + DY2) <= 1E-12) then Exit; NX1 := -DY1 * HalfWidth; NY1 := DX1 * HalfWidth; NX2 := -DY2 * HalfWidth; NY2 := DX2 * HalfWidth; if CrossValue > 0 then begin V1.X := P.X - NX2; V1.Y := P.Y - NY2; V2.X := P.X - NX1; V2.Y := P.Y - NY1; end else begin V1.X := P.X + NX1; V1.Y := P.Y + NY1; V2.X := P.X + NX2; V2.Y := P.Y + NY2; end; AppendTriangle(P, V1, V2, Triangles); end; procedure AppendOpenRun(const A, B: TKMPointF; StartDistance, EndDistance, TotalLength: Double; HalfWidth: Single; var Triangles: TSolidVertexArray); var StartPoint, EndPoint: TKMPointF; begin if TotalLength <= 0 then Exit; StartPoint.X := A.X + (B.X - A.X) * (StartDistance / TotalLength); StartPoint.Y := A.Y + (B.Y - A.Y) * (StartDistance / TotalLength); EndPoint.X := A.X + (B.X - A.X) * (EndDistance / TotalLength); EndPoint.Y := A.Y + (B.Y - A.Y) * (EndDistance / TotalLength); AppendRibbon(StartPoint, EndPoint, HalfWidth, Triangles); end; procedure EmitSolidStroke(const Triangles: TSolidVertexArray; const aColor: TKMColor4f); var I: Integer; begin if Length(Triangles) = 0 then Exit; if Length(Triangles) mod 3 <> 0 then raise Exception.Create('invalid AUX stroke output'); for I := 0 to High(Triangles) do if IsNan(Triangles[I].X) or IsInfinite(Triangles[I].X) or IsNan(Triangles[I].Y) or IsInfinite(Triangles[I].Y) or IsNan(Triangles[I].Z) or IsInfinite(Triangles[I].Z) then raise Exception.Create('non-finite AUX stroke output'); TKMRender.DrawSolidTriangles(Triangles, aColor); end; procedure KMRenderAuxMobileStroke(const aPoints: array of TKMPointF; aClosed: Boolean; aWorldWidth: Single; aPattern: Word; aPatternBitWorldLength: Single; const aColor: TKMColor4f); var Points: TPointArray; Triangles: TSolidVertexArray; I, N: Integer; K: Int64; L, BitLength, S: Double; RunOpen: Boolean; begin if not IsFiniteColor(aColor) then raise Exception.Create('non-finite AUX stroke color'); if IsNan(aWorldWidth) or IsInfinite(aWorldWidth) or (aWorldWidth <= 0) then raise Exception.Create('invalid AUX stroke width'); for I := 0 to High(aPoints) do if not IsFinitePoint(aPoints[I]) then raise Exception.Create('non-finite AUX stroke point'); if (aPattern <> $FFFF) and (aPattern <> 0) then begin if aClosed or (Length(aPoints) <> 2) then raise Exception.Create('invalid patterned AUX stroke'); if IsNan(aPatternBitWorldLength) or IsInfinite(aPatternBitWorldLength) or (aPatternBitWorldLength <= 0) then raise Exception.Create('invalid AUX stroke pattern length'); end; SetLength(Points, 0); for I := 0 to High(aPoints) do if (Length(Points) = 0) or not SameAdjacentPoint(Points[High(Points)], aPoints[I]) then begin SetLength(Points, Length(Points) + 1); Points[High(Points)] := aPoints[I]; end; if aClosed and NearClosingRepeat(Points) then SetLength(Points, Length(Points) - 1); N := Length(Points); if ((not aClosed) and (N < 2)) or (aClosed and (N < 3)) or (aPattern = 0) then Exit; if aClosed then begin I := 2; while I < N do begin if not SameAdjacentPoint(Points[I], Points[0]) and not SameAdjacentPoint(Points[I], Points[1]) then Break; Inc(I); end; if I = N then Exit; end; if aPattern = $FFFF then begin for I := 0 to N - 2 do begin AppendRibbon(Points[I], Points[I + 1], aWorldWidth / 2, Triangles); if I > 0 then AppendBevel(Points[I - 1], Points[I], Points[I + 1], aWorldWidth / 2, Triangles); end; if aClosed then begin AppendRibbon(Points[N - 1], Points[0], aWorldWidth / 2, Triangles); AppendBevel(Points[N - 2], Points[N - 1], Points[0], aWorldWidth / 2, Triangles); AppendBevel(Points[N - 1], Points[0], Points[1], aWorldWidth / 2, Triangles); end; end else begin L := Distance(Points[0], Points[1]); if L = 0 then Exit; BitLength := aPatternBitWorldLength; RunOpen := False; S := 0; K := 0; while K * BitLength < L do begin if ((aPattern shr Integer(K mod 16)) and 1) <> 0 then begin if not RunOpen then S := K * BitLength; RunOpen := True; end else if RunOpen then begin AppendOpenRun(Points[0], Points[1], S, K * BitLength, L, aWorldWidth / 2, Triangles); RunOpen := False; end; Inc(K); end; if RunOpen then AppendOpenRun(Points[0], Points[1], S, L, L, aWorldWidth / 2, Triangles); end; EmitSolidStroke(Triangles, aColor); end; procedure KMRenderAuxMobileFill(const aPoints: array of TKMPointF; const aColor: TKMColor4f); var Points: TPointArray; Remaining: TIndexArray; SolidVertices: TSolidVertexArray; I, J, N, EarIndex, PrevIndex, CurrIndex, NextIndex, PointIndex: Integer; ExpectedVertexCount: Int64; Area, CrossValue, Winding: Double; FoundEar: Boolean; P: TKMPointF; begin for I := 0 to High(aPoints) do if not IsFinitePoint(aPoints[I]) then raise Exception.Create('non-finite AUX fill point'); if not IsFiniteColor(aColor) then raise Exception.Create('non-finite AUX fill color'); SetLength(Points, 0); for I := 0 to High(aPoints) do if (Length(Points) = 0) or not SameAdjacentPoint(Points[High(Points)], aPoints[I]) then begin SetLength(Points, Length(Points) + 1); Points[High(Points)] := aPoints[I]; end; if NearClosingRepeat(Points) then SetLength(Points, Length(Points) - 1); N := Length(Points); if N < 3 then Exit; for I := 0 to N - 1 do for J := I + 1 to N - 1 do if SegmentCrossesOrTouchesNonNeighbor(Points, I, J) then raise Exception.Create('invalid AUX fill perimeter'); Area := 0; for I := 0 to N - 1 do begin J := (I + 1) mod N; Area := Area + Double(Points[I].X) * Points[J].Y - Double(Points[J].X) * Points[I].Y; end; if Abs(Area) / 2 <= 1E-6 then Exit; Winding := 1; if Area < 0 then Winding := -1; if N = 3 then begin AppendTriangle(Points[0], Points[1], Points[2], SolidVertices); end else begin SetLength(Remaining, N); for I := 0 to N - 1 do Remaining[I] := I; while Length(Remaining) > 3 do begin FoundEar := False; for I := 0 to High(Remaining) do begin PrevIndex := (I + Length(Remaining) - 1) mod Length(Remaining); NextIndex := (I + 1) mod Length(Remaining); PrevIndex := Remaining[PrevIndex]; CurrIndex := Remaining[I]; NextIndex := Remaining[NextIndex]; CrossValue := SignedCross(Points[PrevIndex], Points[CurrIndex], Points[NextIndex]); if (Winding * CrossValue <= 0) or (Abs(CrossValue) / 2 <= 1E-6) then Continue; FoundEar := True; for J := 0 to High(Remaining) do begin PointIndex := Remaining[J]; if (PointIndex = PrevIndex) or (PointIndex = CurrIndex) or (PointIndex = NextIndex) then Continue; if PointInOrOnTriangle(Points[PointIndex], Points[PrevIndex], Points[CurrIndex], Points[NextIndex], Winding) then begin FoundEar := False; Break; end; end; if not FoundEar then Continue; AppendTriangle(Points[PrevIndex], Points[CurrIndex], Points[NextIndex], SolidVertices); EarIndex := I; for J := EarIndex to High(Remaining) - 1 do Remaining[J] := Remaining[J + 1]; SetLength(Remaining, Length(Remaining) - 1); FoundEar := True; Break; end; if not FoundEar then raise Exception.Create('invalid polygon triangulation'); end; AppendTriangle(Points[Remaining[0]], Points[Remaining[1]], Points[Remaining[2]], SolidVertices); end; if Length(SolidVertices) mod 3 <> 0 then raise Exception.Create('invalid AUX fill output'); ExpectedVertexCount := Int64(N - 2) * 3; if Length(SolidVertices) <> ExpectedVertexCount then raise Exception.Create('invalid AUX fill output'); for I := 0 to High(SolidVertices) do if IsNan(SolidVertices[I].X) or IsInfinite(SolidVertices[I].X) or IsNan(SolidVertices[I].Y) or IsInfinite(SolidVertices[I].Y) or IsNan(SolidVertices[I].Z) or IsInfinite(SolidVertices[I].Z) then raise Exception.Create('non-finite AUX fill output'); if Length(SolidVertices) = 0 then Exit; TKMRender.DrawSolidTriangles(SolidVertices, aColor); end; end.