draw2d/raster/polygon.go

582 lines
18 KiB
Go

// 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<<SLOPE_FIX_SHIFT
} else {
edges[edgeCount].SlopeFix = 0
}
edgeCount++
} else {
// Clips to left or right or both.
slope := (vertexData[endIndex].X -
vertexData[startIndex].X) /
(vertexData[endIndex].Y -
vertexData[startIndex].Y)
// The edge may clip to both left and right.
// The clip results in one or two new vertices, and one to three segments.
// The rounding for scanlines may produce a result where any of the segments is
// ignored.
// The start is always above the end. Calculate the clip positions to clipVertices.
// It is possible that only one of the vertices exist. This will be detected from the
// clip flags of the vertex later, so they are initialized here.
var clipVertices [2]VertexData
if vertexData[startIndex].X <
vertexData[endIndex].X {
clipVertices[0].X = clipBound[0]
clipVertices[1].X = clipBound[2]
clipVertices[0].ClipFlags = POLYGON_CLIP_LEFT
clipVertices[1].ClipFlags = POLYGON_CLIP_RIGHT
} else {
clipVertices[0].X = clipBound[2]
clipVertices[1].X = clipBound[0]
clipVertices[0].ClipFlags = POLYGON_CLIP_RIGHT
clipVertices[1].ClipFlags = POLYGON_CLIP_LEFT
}
var p int
for p = 0; p < 2; p++ {
// Check if either of the vertices crosses the edge marked for the clip vertex
if clipSum&clipVertices[p].ClipFlags != 0 {
// The the vertex is required, calculate it.
clipVertices[p].Y = vertexData[startIndex].Y +
(clipVertices[p].X-
vertexData[startIndex].X)/slope
// If there is clipping in the vertical direction, the new vertex may be clipped.
if clipSum&(POLYGON_CLIP_TOP|POLYGON_CLIP_BOTTOM) != 0 {
if clipVertices[p].Y < clipBound[1] {
clipVertices[p].ClipFlags = POLYGON_CLIP_TOP
clipVertices[p].Line = int(clipBound[1])
} else if clipVertices[p].Y > 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_SHIFT
} else {
edges[edgeCount].SlopeFix = 0
}
edgeCount++
}
} else {
// Clip at either side, check which side. The clip flag is on for the vertex
// that doesn't exist, i.e. has not been clipped to be inside the rect.
if clipVertices[0].ClipFlags&(POLYGON_CLIP_LEFT|POLYGON_CLIP_RIGHT) != 0 {
topClipIndex = 1
bottomClipIndex = 1
} else {
topClipIndex = 0
bottomClipIndex = 0
}
}
// Generate the edges from start - clip top and clip bottom - end
// Clip top and clip bottom may be the same vertex if there is only one
// clipped vertex.
// Check that the line is different for the vertices.
if vertexData[startIndex].Line != clipVertices[topClipIndex].Line {
edges[edgeCount].FirstLine = firstLine
edges[edgeCount].LastLine = clipVertices[topClipIndex].Line
edges[edgeCount].Winding = winding
// If startIndex is clipped, the edge is a vertical one.
if vertexData[startIndex].ClipFlags&(POLYGON_CLIP_LEFT|POLYGON_CLIP_RIGHT) != 0 {
edges[edgeCount].X = Fix(clipVertices[topClipIndex].X * FIXED_FLOAT_COEF)
edges[edgeCount].Slope = 0
edges[edgeCount].SlopeFix = 0
} else {
startx := vertexData[startIndex].X +
(float64(firstLine)-vertexData[startIndex].Y)*slope
edges[edgeCount].X = Fix(startx * FIXED_FLOAT_COEF)
edges[edgeCount].Slope = Fix(slope * FIXED_FLOAT_COEF)
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_SHIFT
} else {
edges[edgeCount].SlopeFix = 0
}
}
edgeCount++
}
// Check that the line is different for the vertices.
if clipVertices[bottomClipIndex].Line != vertexData[endIndex].Line {
firstClipLine := clipVertices[bottomClipIndex].Line + 1
edges[edgeCount].FirstLine = firstClipLine
edges[edgeCount].LastLine = lastLine
edges[edgeCount].Winding = winding
// If endIndex is clipped, the edge is a vertical one.
if vertexData[endIndex].ClipFlags&(POLYGON_CLIP_LEFT|POLYGON_CLIP_RIGHT) != 0 {
edges[edgeCount].X = Fix(clipVertices[bottomClipIndex].X * FIXED_FLOAT_COEF)
edges[edgeCount].Slope = 0
edges[edgeCount].SlopeFix = 0
} else {
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)
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_SHIFT
} else {
edges[edgeCount].SlopeFix = 0
}
}
edgeCount++
}
}
}
}
return edgeCount
}