// Copyright 2011 The draw2d Authors. All rights reserved. // created: 27/05/2011 by Laurent Le Goff package raster const ( POLYGON_CLIP_NONE = iota POLYGON_CLIP_LEFT POLYGON_CLIP_RIGHT POLYGON_CLIP_TOP POLYGON_CLIP_BOTTOM ) type Polygon []float64 type PolygonEdge struct { X, Slope float64 FirstLine, LastLine int Winding int16 } //! A more optimized representation of a polygon edge. type PolygonScanEdge struct { FirstLine, LastLine int Winding int16 X Fix Slope Fix SlopeFix Fix NextEdge *PolygonScanEdge } //! Calculates the edges of the polygon with transformation and clipping to edges array. /*! \param startIndex the index for the first vertex. * \param vertexCount the amount of vertices to convert. * \param edges the array for result edges. This should be able to contain 2*aVertexCount edges. * \param tr the transformation matrix for the polygon. * \param aClipRectangle the clip rectangle. * \return the amount of edges in the result. */ func (p Polygon) getEdges(startIndex, vertexCount int, edges []PolygonEdge, tr [6]float64, clipBound [4]float64) int { startIndex = startIndex * 2 endIndex := startIndex + vertexCount*2 if endIndex > len(p) { endIndex = len(p) } x := p[startIndex] y := p[startIndex+1] // inline transformation prevX := x*tr[0] + y*tr[2] + tr[4] prevY := x*tr[1] + y*tr[3] + tr[5] //! Calculates the clip flags for a point. prevClipFlags := POLYGON_CLIP_NONE if prevX < clipBound[0] { prevClipFlags |= POLYGON_CLIP_LEFT } else if prevX >= clipBound[2] { prevClipFlags |= POLYGON_CLIP_RIGHT } if prevY < clipBound[1] { prevClipFlags |= POLYGON_CLIP_TOP } else if prevY >= clipBound[3] { prevClipFlags |= POLYGON_CLIP_BOTTOM } edgeCount := 0 var k, clipFlags, clipSum, clipUnion int var xleft, yleft, xright, yright, oldY, maxX, minX float64 var swapWinding int16 for n := startIndex; n < endIndex; n = n + 2 { k = (n + 2) % len(p) x = p[k]*tr[0] + p[k+1]*tr[2] + tr[4] y = p[k]*tr[1] + p[k+1]*tr[3] + tr[5] //! Calculates the clip flags for a point. clipFlags = POLYGON_CLIP_NONE if prevX < clipBound[0] { clipFlags |= POLYGON_CLIP_LEFT } else if prevX >= clipBound[2] { clipFlags |= POLYGON_CLIP_RIGHT } if prevY < clipBound[1] { clipFlags |= POLYGON_CLIP_TOP } else if prevY >= clipBound[3] { clipFlags |= POLYGON_CLIP_BOTTOM } clipSum = prevClipFlags | clipFlags clipUnion = prevClipFlags & clipFlags // Skip all edges that are either completely outside at the top or at the bottom. if clipUnion&(POLYGON_CLIP_TOP|POLYGON_CLIP_BOTTOM) == 0 { if clipUnion&POLYGON_CLIP_RIGHT != 0 { // Both clip to right, edge is a vertical line on the right side if getVerticalEdge(prevY, y, clipBound[2], &edges[edgeCount], clipBound) { edgeCount++ } } else if clipUnion&POLYGON_CLIP_LEFT != 0 { // Both clip to left, edge is a vertical line on the left side if getVerticalEdge(prevY, y, clipBound[0], &edges[edgeCount], clipBound) { edgeCount++ } } else if clipSum&(POLYGON_CLIP_RIGHT|POLYGON_CLIP_LEFT) == 0 { // No clipping in the horizontal direction if getEdge(prevX, prevY, x, y, &edges[edgeCount], clipBound) { edgeCount++ } } else { // Clips to left or right or both. if x < prevX { xleft, yleft = x, y xright, yright = prevX, prevY swapWinding = -1 } else { xleft, yleft = prevX, prevY xright, yright = x, y swapWinding = 1 } slope := (yright - yleft) / (xright - xleft) if clipSum&POLYGON_CLIP_RIGHT != 0 { // calculate new position for the right vertex oldY = yright maxX = clipBound[2] yright = yleft + (maxX-xleft)*slope xright = maxX // add vertical edge for the overflowing part if getVerticalEdge(yright, oldY, maxX, &edges[edgeCount], clipBound) { edges[edgeCount].Winding *= swapWinding edgeCount++ } } if clipSum&POLYGON_CLIP_LEFT != 0 { // calculate new position for the left vertex oldY = yleft minX = clipBound[0] yleft = yleft + (minX-xleft)*slope xleft = minX // add vertical edge for the overflowing part if getVerticalEdge(oldY, yleft, minX, &edges[edgeCount], clipBound) { edges[edgeCount].Winding *= swapWinding edgeCount++ } } if getEdge(xleft, yleft, xright, yright, &edges[edgeCount], clipBound) { edges[edgeCount].Winding *= swapWinding edgeCount++ } } } prevClipFlags = clipFlags prevX = x prevY = y } return edgeCount } //! Creates a polygon edge between two vectors. /*! Clips the edge vertically to the clip rectangle. Returns true for edges that * should be rendered, false for others. */ func getEdge(x0, y0, x1, y1 float64, edge *PolygonEdge, clipBound [4]float64) bool { var startX, startY, endX, endY float64 var winding int16 if y0 <= y1 { startX = x0 startY = y0 endX = x1 endY = y1 winding = 1 } else { startX = x1 startY = y1 endX = x0 endY = y0 winding = -1 } // Essentially, firstLine is floor(startY + 1) and lastLine is floor(endY). // These are refactored to integer casts in order to avoid function // calls. The difference with integer cast is that numbers are always // rounded towards zero. Since values smaller than zero get clipped away, // only coordinates between 0 and -1 require greater attention as they // also round to zero. The problems in this range can be avoided by // adding one to the values before conversion and subtracting after it. firstLine := int(startY + 1) lastLine := int(endY+1) - 1 minClip := int(clipBound[1]) maxClip := int(clipBound[3]) // If start and end are on the same line, the edge doesn't cross // any lines and thus can be ignored. // If the end is smaller than the first line, edge is out. // If the start is larger than the last line, edge is out. if firstLine > lastLine || lastLine < minClip || firstLine >= maxClip { return false } // Adjust the start based on the target. if firstLine < minClip { firstLine = minClip } if lastLine >= maxClip { lastLine = maxClip - 1 } edge.Slope = (endX - startX) / (endY - startY) edge.X = startX + (float64(firstLine)-startY)*edge.Slope edge.Winding = winding edge.FirstLine = firstLine edge.LastLine = lastLine return true } //! Creates a vertical polygon edge between two y values. /*! Clips the edge vertically to the clip rectangle. Returns true for edges that * should be rendered, false for others. */ func getVerticalEdge(startY, endY, x float64, edge *PolygonEdge, clipBound [4]float64) bool { var start, end float64 var winding int16 if startY < endY { start = startY end = endY winding = 1 } else { start = endY end = startY winding = -1 } firstLine := int(start + 1) lastLine := int(end+1) - 1 minClip := int(clipBound[1]) maxClip := int(clipBound[3]) // If start and end are on the same line, the edge doesn't cross // any lines and thus can be ignored. // If the end is smaller than the first line, edge is out. // If the start is larger than the last line, edge is out. if firstLine > lastLine || lastLine < minClip || firstLine >= maxClip { return false } // Adjust the start based on the clip rect. if firstLine < minClip { firstLine = minClip } if lastLine >= maxClip { lastLine = maxClip - 1 } edge.Slope = 0 edge.X = x edge.Winding = winding edge.FirstLine = firstLine edge.LastLine = lastLine return true } type VertexData struct { X, Y float64 ClipFlags int Line int } //! Calculates the edges of the polygon with transformation and clipping to edges array. /*! Note that this may return upto three times the amount of edges that the polygon has vertices, * in the unlucky case where both left and right side get clipped for all edges. * \param edges the array for result edges. This should be able to contain 2*aVertexCount edges. * \param aTransformation the transformation matrix for the polygon. * \param aClipRectangle the clip rectangle. * \return the amount of edges in the result. */ func (p Polygon) getScanEdges(edges []PolygonScanEdge, tr [6]float64, clipBound [4]float64) int { var n int vertexData := make([]VertexData, len(p)/2+1) for n = 0; n < len(vertexData)-1; n = n + 1 { k := n * 2 vertexData[n].X = p[k]*tr[0] + p[k+1]*tr[2] + tr[4] vertexData[n].Y = p[k]*tr[1] + p[k+1]*tr[3] + tr[5] // Calculate clip flags for all vertices. vertexData[n].ClipFlags = POLYGON_CLIP_NONE if vertexData[n].X < clipBound[0] { vertexData[n].ClipFlags |= POLYGON_CLIP_LEFT } else if vertexData[n].X >= clipBound[2] { vertexData[n].ClipFlags |= POLYGON_CLIP_RIGHT } if vertexData[n].Y < clipBound[1] { vertexData[n].ClipFlags |= POLYGON_CLIP_TOP } else if vertexData[n].Y >= clipBound[3] { vertexData[n].ClipFlags |= POLYGON_CLIP_BOTTOM } // Calculate line of the vertex. If the vertex is clipped by top or bottom, the line // is determined by the clip rectangle. if vertexData[n].ClipFlags&POLYGON_CLIP_TOP != 0 { vertexData[n].Line = int(clipBound[1]) } else if vertexData[n].ClipFlags&POLYGON_CLIP_BOTTOM != 0 { vertexData[n].Line = int(clipBound[3] - 1) } else { vertexData[n].Line = int(vertexData[n].Y+1) - 1 } } // Copy the data from 0 to the last entry to make the data to loop. vertexData[len(vertexData)-1] = vertexData[0] // Transform the first vertex; store. // Process mVertexCount - 1 times, next is n+1 // copy the first vertex to // Process 1 time, next is n edgeCount := 0 for n = 0; n < len(vertexData)-1; n++ { clipSum := vertexData[n].ClipFlags | vertexData[n+1].ClipFlags clipUnion := vertexData[n].ClipFlags & vertexData[n+1].ClipFlags if clipUnion&(POLYGON_CLIP_TOP|POLYGON_CLIP_BOTTOM) == 0 && vertexData[n].Line != vertexData[n+1].Line { var startIndex, endIndex int var winding int16 if vertexData[n].Y < vertexData[n+1].Y { startIndex = n endIndex = n + 1 winding = 1 } else { startIndex = n + 1 endIndex = n winding = -1 } firstLine := vertexData[startIndex].Line + 1 lastLine := vertexData[endIndex].Line if clipUnion&POLYGON_CLIP_RIGHT != 0 { // Both clip to right, edge is a vertical line on the right side edges[edgeCount].FirstLine = firstLine edges[edgeCount].LastLine = lastLine edges[edgeCount].Winding = winding edges[edgeCount].X = Fix(clipBound[2] * FIXED_FLOAT_COEF) edges[edgeCount].Slope = 0 edges[edgeCount].SlopeFix = 0 edgeCount++ } else if clipUnion&POLYGON_CLIP_LEFT != 0 { // Both clip to left, edge is a vertical line on the left side edges[edgeCount].FirstLine = firstLine edges[edgeCount].LastLine = lastLine edges[edgeCount].Winding = winding edges[edgeCount].X = Fix(clipBound[0] * FIXED_FLOAT_COEF) edges[edgeCount].Slope = 0 edges[edgeCount].SlopeFix = 0 edgeCount++ } else if clipSum&(POLYGON_CLIP_RIGHT|POLYGON_CLIP_LEFT) == 0 { // No clipping in the horizontal direction slope := (vertexData[endIndex].X - vertexData[startIndex].X) / (vertexData[endIndex].Y - vertexData[startIndex].Y) // If there is vertical clip (for the top) it will be processed here. The calculation // should be done for all non-clipping edges as well to determine the accurate position // where the edge crosses the first scanline. startx := vertexData[startIndex].X + (float64(firstLine)-vertexData[startIndex].Y)*slope edges[edgeCount].FirstLine = firstLine edges[edgeCount].LastLine = lastLine edges[edgeCount].Winding = winding edges[edgeCount].X = Fix(startx * FIXED_FLOAT_COEF) edges[edgeCount].Slope = Fix(slope * FIXED_FLOAT_COEF) if lastLine-firstLine >= SLOPE_FIX_STEP { edges[edgeCount].SlopeFix = Fix(slope*SLOPE_FIX_STEP*FIXED_FLOAT_COEF) - edges[edgeCount].Slope< clipBound[3] { clipVertices[p].ClipFlags = POLYGON_CLIP_BOTTOM clipVertices[p].Line = int(clipBound[3] - 1) } else { clipVertices[p].ClipFlags = 0 clipVertices[p].Line = int(clipVertices[p].Y+1) - 1 } } else { clipVertices[p].ClipFlags = 0 clipVertices[p].Line = int(clipVertices[p].Y+1) - 1 } } } // Now there are three or four vertices, in the top-to-bottom order of start, clip0, clip1, // end. What kind of edges are required for connecting these can be determined from the // clip flags. // -if clip vertex has horizontal clip flags, it doesn't exist. No edge is generated. // -if start vertex or end vertex has horizontal clip flag, the edge to/from the clip vertex is vertical // -if the line of two vertices is the same, the edge is not generated, since the edge doesn't // cross any scanlines. // The alternative patterns are: // start - clip0 - clip1 - end // start - clip0 - end // start - clip1 - end var topClipIndex, bottomClipIndex int if (clipVertices[0].ClipFlags|clipVertices[1].ClipFlags)& (POLYGON_CLIP_LEFT|POLYGON_CLIP_RIGHT) == 0 { // Both sides are clipped, the order is start-clip0-clip1-end topClipIndex = 0 bottomClipIndex = 1 // Add the edge from clip0 to clip1 // Check that the line is different for the vertices. if clipVertices[0].Line != clipVertices[1].Line { firstClipLine := clipVertices[0].Line + 1 startx := vertexData[startIndex].X + (float64(firstClipLine)-vertexData[startIndex].Y)*slope edges[edgeCount].X = Fix(startx * FIXED_FLOAT_COEF) edges[edgeCount].Slope = Fix(slope * FIXED_FLOAT_COEF) edges[edgeCount].FirstLine = firstClipLine edges[edgeCount].LastLine = clipVertices[1].Line edges[edgeCount].Winding = winding if edges[edgeCount].LastLine-edges[edgeCount].FirstLine >= SLOPE_FIX_STEP { edges[edgeCount].SlopeFix = Fix(slope*SLOPE_FIX_STEP*FIXED_FLOAT_COEF) - edges[edgeCount].Slope<= SLOPE_FIX_STEP { edges[edgeCount].SlopeFix = Fix(slope*SLOPE_FIX_STEP*FIXED_FLOAT_COEF) - edges[edgeCount].Slope<= SLOPE_FIX_STEP { edges[edgeCount].SlopeFix = Fix(slope*SLOPE_FIX_STEP*FIXED_FLOAT_COEF) - edges[edgeCount].Slope<