library Polygon uses PositionFunctions
//! runtextmacro NewLinkedList("polygonList")
/** <to-do>
* - public method split takes nothing returns Polygon[]
* - public method add takes Polygon p returns nothing
* - public method substract takes Polygon p returns nothing
* - public method exclusiveOr takes Polygon p returns nothing
* - public method intersect takes Polygon p returns nothing
* - public static method add takes Polygon p1, Polygon p2, Polygon dest returns Polygon
* - public static method substract takes Polygon p1, Polygon p2, Polygon dest returns Polygon
* - public static method exclusiveOr takes Polygon p1, Polygon p2, Polygon dest returns Polygon
* - public static method intersect takes Polygon p1, Polygon p2, Polygon dest returns Polygon
* - implement trigger calls for some functions to prevent OP limits
*/
struct Polygon
//Data storage for each line.
private static real array line_startX
private static real array line_startY
private static real array line_endX
private static real array line_endY
//Data storage for each polygon.
private real rotation
private real scaling
private real minX
private real minY
private real maxX
private real maxY
private real centerX
private real centerY
private integer numberOfLines
//private static LinkedList polygonList
public static method create takes nothing returns Polygon
local Polygon this = Polygon.allocate()
set this.numberOfLines = 0
set this.minX = 0
set this.minY = 0
set this.maxX = 0
set this.maxY = 0
set this.centerX = 0
set this.centerY = 0
set this.scaling = 1
return this
endmethod
public method clear takes nothing returns nothing
if polygonList.prepareIterator(this) then
loop
exitwhen not polygonList.next(this)
set line_startX[polygonList.iterator] = 0
set line_startY[polygonList.iterator] = 0
set line_endX[polygonList.iterator] = 0
set line_endY[polygonList.iterator] = 0
call polygonList.removeIndex(polygonList.iterator)
endloop
endif
set this.numberOfLines = 0
set this.minX = 0
set this.minY = 0
set this.maxX = 0
set this.maxY = 0
set this.centerX = 0
set this.centerY = 0
set this.scaling = 0
endmethod
public method remove takes nothing returns nothing
call this.clear()
call this.deallocate()
endmethod
public method getBounds takes nothing returns rect
call SetRect(RECT, this.minX, this.minY, this.maxX, this.maxY)
return RECT
endmethod
public method getCenter takes nothing returns Coord
return Coord.create(this.centerX, this.centerY)
endmethod
public method insertLine takes real startX, real startY, real endX, real endY, integer index returns integer
local integer id = polygonList.insertIndex(this, index)
set line_startX[id] = startX
set line_startY[id] = startY
set line_endX[id] = endX
set line_endY[id] = endY
if this.numberOfLines == 0 then
if startX < endX then
set this.minX = startX
set this.maxX = endX
else
set this.minX = endX
set this.maxX = startX
endif
if startY < endY then
set this.minY = startY
set this.maxY = endY
else
set this.minY = endY
set this.maxY = startY
endif
set this.centerX = (startX + endX)/2
set this.centerY = (startY + endY)/2
else
if startX < this.minX then
set this.minX = startX
elseif startX > this.maxX then
set this.maxX = startX
endif
if endX < this.minX then
set this.minX = endX
elseif endX > this.maxX then
set this.maxX = endX
endif
if startY < this.minY then
set this.minY = startY
elseif startY > this.maxY then
set this.maxY = startY
endif
if endY < this.minY then
set this.minY = endY
elseif endY > this.maxY then
set this.maxY = endY
endif
set this.centerX = (this.centerX*this.numberOfLines*2 + startX + endX) / ((numberOfLines+1)*2)
set this.centerY = (this.centerY*this.numberOfLines*2 + startY + endY) / ((numberOfLines+1)*2)
endif
set this.numberOfLines = this.numberOfLines +1
return id
endmethod
public method addLine takes real startX, real startY, real endX, real endY returns integer
return insertLine(startX, startY, endX, endY, 0)
endmethod
public method move takes real xOffset, real yOffset returns nothing
set this.minX = this.minX + xOffset
set this.minY = this.minY + yOffset
set this.maxX = this.maxX + xOffset
set this.maxY = this.maxY + yOffset
set this.centerX = this.centerX + xOffset
set this.centerY = this.centerY + yOffset
if polygonList.prepareIterator(this) then
loop
exitwhen not polygonList.next(this)
set line_startX[polygonList.iterator] = line_startX[polygonList.iterator] + xOffset
set line_startY[polygonList.iterator] = line_startY[polygonList.iterator] + yOffset
set line_endX[polygonList.iterator] = line_endX[polygonList.iterator] + xOffset
set line_endY[polygonList.iterator] = line_endY[polygonList.iterator] + yOffset
endloop
endif
endmethod
public method setPosition takes real x, real y returns nothing
call this.move(x - this.centerX, y - this.centerY)
endmethod
public method rotateAroundPoint takes real angle, real originX, real originY returns nothing
local real sin = Sin(angle)
local real cos = Cos(angle)
local real oldX
local real oldY
set this.rotation = this.rotation + angle
set this.centerX = (originX + ((this.centerX - originX) * cos) - ((this.centerY - originY) * sin))
set this.centerY = (originY + ((this.centerX - originX) * sin) + ((this.centerY - originY) * cos))
set this.minX = this.centerX
set this.minY = this.centerY
set this.maxX = this.centerX
set this.maxY = this.centerY
if polygonList.prepareIterator(this) then
loop
exitwhen not polygonList.next(this)
set oldX = line_startX[polygonList.iterator]
set oldY = line_startY[polygonList.iterator]
set line_startX[polygonList.iterator] = (originX + ((oldX - originX) * cos) - ((oldY - originY) * sin))
set line_startY[polygonList.iterator] = (originY + ((oldX - originX) * sin) + ((oldY - originY) * cos))
set oldX = line_endX[polygonList.iterator]
set oldY = line_endY[polygonList.iterator]
set line_endX[polygonList.iterator] = (originX + ((oldX - originX) * cos) - ((oldY - originY) * sin))
set line_endY[polygonList.iterator] = (originY + ((oldX - originX) * sin) + ((oldY - originY) * cos))
if line_startX[polygonList.iterator] < this.minX then
set this.minX = line_startX[polygonList.iterator]
elseif line_startX[polygonList.iterator] > this.maxX then
set this.maxX = line_startX[polygonList.iterator]
endif
if line_endX[polygonList.iterator] < this.minX then
set this.minX = line_endX[polygonList.iterator]
elseif line_endX[polygonList.iterator] > this.maxX then
set this.maxX = line_endX[polygonList.iterator]
endif
if line_startY[polygonList.iterator] < this.minY then
set this.minY = line_startY[polygonList.iterator]
elseif line_startY[polygonList.iterator] > this.maxY then
set this.maxY = line_startY[polygonList.iterator]
endif
if line_endY[polygonList.iterator] < this.minY then
set this.minY = line_endY[polygonList.iterator]
elseif line_endY[polygonList.iterator] > this.maxY then
set this.maxY = line_endY[polygonList.iterator]
endif
endloop
endif
endmethod
public method rotateAroundPointDeg takes real angle, real originX, real originY returns nothing
call this.rotateAroundPoint(angle*DEGTORAD, originX, originY)
endmethod
public method rotate takes real angle returns nothing
call this.rotateAroundPoint(angle, this.centerX, this.centerY)
endmethod
public method rotateDeg takes real angle returns nothing
call this.rotate(angle*DEGTORAD)
endmethod
public method setRotation takes real angle returns nothing
call this.rotate(angle - this.rotation)
endmethod
public method setRotationDeg takes real angle returns nothing
call this.setRotation(angle*DEGTORAD)
endmethod
public method rotateAsDefault takes real angle returns nothing
call this.rotate(angle)
set this.rotation = 0
endmethod
public method rotateAsDefaultDeg takes real angle returns nothing
call this.rotateAsDefault(angle*DEGTORAD)
endmethod
public method setRotationAsDefault takes real angle returns nothing
call this.rotateAsDefault(angle - this.rotation)
endmethod
public method setRotationAsDefaultDeg takes real angle returns nothing
call this.setRotationAsDefault(angle*DEGTORAD)
endmethod
public method getRotation takes nothing returns real
return this.rotation
endmethod
public method getRotationDeg takes nothing returns real
return this.rotation*RADTODEG
endmethod
public method scale takes real scale returns nothing
set this.scaling = this.scaling * scale
set this.minX = this.centerX - (this.minX - this.centerX)*scale
set this.minY = this.centerY - (this.minY - this.centerY)*scale
set this.maxX = this.centerX - (this.maxX - this.centerX)*scale
set this.maxY = this.centerY - (this.maxY - this.centerY)*scale
if polygonList.prepareIterator(this) then
loop
exitwhen not polygonList.next(this)
set line_startX[polygonList.iterator] = this.centerX - (line_startX[polygonList.iterator] - this.centerX)*scale
set line_startY[polygonList.iterator] = this.centerX - (line_startY[polygonList.iterator] - this.centerY)*scale
set line_endX[polygonList.iterator] = this.centerX - (line_endX[polygonList.iterator] - this.centerX)*scale
set line_endY[polygonList.iterator] = this.centerX - (line_endY[polygonList.iterator] - this.centerY)*scale
endloop
endif
endmethod
public method setScale takes real scale returns nothing
call this.scale(scale / this.scaling)
endmethod
public method scaleAsDefault takes real scale returns nothing
call this.scale(scale)
set this.scaling = 1
endmethod
public method setScaleAsDefault takes real scale returns nothing
call this.scaleAsDefault(scale / this.scaling)
endmethod
public method getScale takes nothing returns real
return this.scaling
endmethod
//Point in Polygon iteration variables.
private static integer pip_hits
private static real pip_leftX
private static real pip_testX
private static real pip_testY
private method containsPointIteration takes real px, real py returns nothing
if line_endY[polygonList.iterator] == line_startY[polygonList.iterator] then
return
endif
if line_endX[polygonList.iterator] < line_startX[polygonList.iterator] then
if px >= line_startX[polygonList.iterator] then
return
endif
set pip_leftX = line_endX[polygonList.iterator]
else
if px >= line_endX[polygonList.iterator] then
return
endif
set pip_leftX = line_startX[polygonList.iterator]
endif
if line_endY[polygonList.iterator] < line_startY[polygonList.iterator] then
if py < line_endY[polygonList.iterator] or py >= line_startY[polygonList.iterator] then
return
endif
if (px < pip_leftX) then
set pip_hits = pip_hits +1
return
endif
set pip_testX = px - line_endX[polygonList.iterator]
set pip_testY = py - line_endY[polygonList.iterator]
else
if py < line_startY[polygonList.iterator] or py >= line_endY[polygonList.iterator] then
return
endif
if px < pip_leftX then
set pip_hits = pip_hits +1
return
endif
set pip_testX = px - line_startX[polygonList.iterator]
set pip_testY = py - line_startY[polygonList.iterator]
endif
if pip_testX < (pip_testY / (line_startY[polygonList.iterator] - line_endY[polygonList.iterator]) * (line_startX[polygonList.iterator] - line_endX[polygonList.iterator])) then
set pip_hits = pip_hits +1
endif
endmethod
public method containsPoint takes real px, real py returns boolean
set pip_hits = 0
if polygonList.prepareIterator(this) then
loop
exitwhen not polygonList.next(this)
call this.containsPointIteration(px, py)
endloop
endif
return pip_hits - pip_hits/2*2 != 0
endmethod
public method getSurface takes nothing returns real
//Method 1: All polygons must be clockwise.
//Method 2: All polygons must be clockwise.
/* Method 1:
local real area = 0
if polygonList.prepareIterator(this) then
loop
exitwhen not polygonList.next(this)
set area = area + (line_startX[polygonList.iterator] + line_endX[polygonList.iterator]) * (line_startY[polygonList.iterator] - line_endY[polygonList.iterator])
endloop
endif
return RAbsBJ(area/2)
*/
/* Method 2:
*/
local real area = 0
if polygonList.prepareIterator(this) then
loop
exitwhen not polygonList.next(this)
set area = area + line_endX[polygonList.iterator] * line_startY[polygonList.iterator] - line_startX[polygonList.iterator] * line_endY[polygonList.iterator]
endloop
endif
return RAbsBJ(area/2)
endmethod
public method clone takes nothing returns Polygon
local Polygon poly = Polygon.create()
local integer id
if polygonList.prepareIterator(this) then
loop
exitwhen not polygonList.next(this)
set id = polygonList.insertIndex(poly, 0)
set line_startX[id] = line_startX[polygonList.iterator]
set line_startY[id] = line_startY[polygonList.iterator]
set line_endX[id] = line_endX[polygonList.iterator]
set line_endY[id] = line_endY[polygonList.iterator]
endloop
endif
set poly.numberOfLines = this.numberOfLines
set poly.minX = this.minX
set poly.minY = this.minY
set poly.maxX = this.maxX
set poly.maxY = this.maxY
set poly.centerX = this.centerX
set poly.centerY = this.centerY
set poly.scaling = this.scaling
return poly
endmethod
public static integer pointCount
public static real array pointX
public static real array pointY
private method getLineIntersection takes integer i1, integer i2 returns boolean
local real s1_x
local real s1_y
local real s2_x
local real s2_y
local real s
local real t
local real a
set s1_x = line_endX[i1] - line_startX[i1]
set s1_y = line_endY[i1] - line_startY[i1]
set s2_x = line_endX[i2] - line_startX[i2]
set s2_y = line_endY[i2] - line_startY[i2]
set a = -s2_x * s1_y + s1_x * s2_y
if a == 0 then
return false
endif
set s = (-s1_y * (line_startX[i1] - line_startX[i2]) + s1_x * (line_startY[i1] - line_startY[i2])) / a
set t = ( s2_x * (line_startY[i1] - line_startY[i2]) - s2_y * (line_startX[i1] - line_startX[i2])) / a
if s >= 0 and s <= 1 and t >= 0 and t <= 1 then
// Collision detected
set pointX[pointCount] = line_startX[i1] + t*s1_x
set pointY[pointCount] = line_startY[i1] + t*s1_y
set pointCount = pointCount +1
return true
endif
return false
endmethod
public method getIntersectionPoints takes Polygon p returns nothing
local integer i1
local integer i2
set pointCount = 0
if polygonList.prepareIterator(this) then
loop
exitwhen not polygonList.next(this)
set i1 = polygonList.iterator
call polygonList.prepareIterator(p)
loop
exitwhen not polygonList.next(p)
set i2 = polygonList.iterator
call getLineIntersection(i1, i2)
endloop
endloop
endif
endmethod
public method toString takes nothing returns string
local string result = ""
if polygonList.prepareIterator(this) then
loop
exitwhen not polygonList.next(this)
set result = result + ", ["+Coordinates2String(line_startX[polygonList.iterator], line_startY[polygonList.iterator])+", "+Coordinates2String(line_endX[polygonList.iterator], line_endY[polygonList.iterator])+"]"
endloop
endif
return "[ "+SubString(result, 2, StringLength(result))+" ]"
endmethod
//Test method
public method generateLightnings takes string model, real duration returns nothing
local lightning l
if polygonList.prepareIterator(this) then
loop
exitwhen not polygonList.next(this)
set l = AddLightning(model, true, line_startX[polygonList.iterator], line_startY[polygonList.iterator], line_endX[polygonList.iterator], line_endY[polygonList.iterator])
call TimedL.P2P(l, duration, 100, 100)
set l = null
endloop
endif
endmethod
//Test method
public static method createStar takes integer numberOfPoints, real centerX, real centerY, real outerRadius, real innerRadius returns Polygon
local Polygon this = Polygon.create()
local real angleStep = TAU/numberOfPoints/2
local integer i = 0
local real angle = 0
local real startX
local real startY
local real endX
local real endY
set startX = centerX + innerRadius * Cos(-angleStep)
set startY = centerY + innerRadius * Sin(-angleStep)
loop
exitwhen i >= numberOfPoints
set endX = centerX + outerRadius * Cos(angle)
set endY = centerY + outerRadius * Sin(angle)
call this.addLine(startX, startY, endX, endY)
set startX = endX
set startY = endY
set angle = angle + angleStep
set endX = centerX + innerRadius * Cos(angle)
set endY = centerY + innerRadius * Sin(angle)
call this.addLine(startX, startY, endX, endY)
set startX = endX
set startY = endY
set angle = angle + angleStep
set i = i +1
endloop
return this
endmethod
//Test method
public static method createCircle takes integer numberOfVertices, real centerX, real centerY, real radius returns Polygon
local Polygon this = Polygon.create()
local real angleStep = TAU/numberOfVertices
local integer i = 0
local real angle = 0
local real startX
local real startY
local real endX
local real endY
set startX = centerX + radius * Cos(-angleStep)
set startY = centerX + radius * Sin(-angleStep)
loop
exitwhen i >= numberOfVertices
set endX = centerX + radius * Cos(angle)
set endY = centerY + radius * Sin(angle)
call this.addLine(startX, startY, endX, endY)
set startX = endX
set startY = endY
set angle = angle + angleStep
set i = i +1
endloop
return this
endmethod
endstruct
endlibrary