forked from Mirrors/openclonk
427 lines
11 KiB
C
427 lines
11 KiB
C
/**
|
|
Library for shapes
|
|
See docs/sdk/script/Shape.xml for documentation.
|
|
|
|
@author Sven2
|
|
|
|
*/
|
|
|
|
/* Base shape */
|
|
|
|
local BaseShape;
|
|
|
|
// Returns the area in squared pixels covered by the shape. Works for all specific shapes by using their functionality.
|
|
public func GetArea()
|
|
{
|
|
var check_rect = this->GetBoundingRectangle();
|
|
var area = 0;
|
|
for (var x = check_rect.x; x < check_rect.x + check_rect.wdt; x++)
|
|
for (var y = check_rect.y; y < check_rect.y + check_rect.hgt; y++)
|
|
if (this->IsPointContained(x, y))
|
|
area++;
|
|
return area;
|
|
}
|
|
|
|
/* Rectangle shape */
|
|
|
|
local BaseRectangle; // properties x,y,w,h
|
|
|
|
// Point contained in rectangle?
|
|
private func BaseRectangle_IsPointContained(int x, int y)
|
|
{
|
|
//return ((x-this.x+this.wdt)/this.wdt) * ((y-this.y+this.hgt)/this.hgt) == 1 && x>=this.x;
|
|
return x>=this.x && y>=this.y && x<this.x+this.wdt && y<this.y+this.hgt;
|
|
}
|
|
|
|
// bounding rectangle is just self
|
|
private func BaseRectangle_GetBoundingRectangle() { return this; }
|
|
|
|
private func BaseRectangle_Find_In(context)
|
|
{
|
|
if (!context) context = Global;
|
|
return context->Find_InRect(this.x, this.y, this.wdt, this.hgt);
|
|
}
|
|
|
|
private func BaseRectangle_Find_At(context)
|
|
{
|
|
if (!context) context = Global;
|
|
return context->Find_AtRect(this.x, this.y, this.wdt, this.hgt);
|
|
}
|
|
|
|
private func BaseRectangle_GetRandomPoint(proplist result)
|
|
{
|
|
if (this.wdt<=0 || this.hgt <=0) return false;
|
|
result.x = this.x + Random(this.wdt);
|
|
result.y = this.y + Random(this.hgt);
|
|
return true;
|
|
}
|
|
|
|
private func BaseRectangle_GetArea()
|
|
{
|
|
return this.wdt * this.hgt;
|
|
}
|
|
|
|
private func BaseRectangle_ToString()
|
|
{
|
|
return Format("Shape->Rectangle(%d, %d, %d, %d)", this.x, this.y, this.wdt, this.hgt);
|
|
}
|
|
|
|
private func BaseRectangle_IsFullMap()
|
|
{
|
|
return !this.x && !this.y && this.wdt == LandscapeWidth() && this.hgt == LandscapeHeight();
|
|
}
|
|
|
|
/** Constructor of rectangle area. (x,y) is included; (x+w,y+h) is excluded.
|
|
@par x Global left side of rectangle
|
|
@par y Global top side of rectangle
|
|
@par w Rectangle width
|
|
@par h Rectangle height
|
|
@return return a shape proplist representing a rectangle
|
|
*/
|
|
public func Rectangle(int x, int y, int w, int h)
|
|
{
|
|
return new BaseRectangle { x=x, y=y, wdt=w, hgt=h };
|
|
}
|
|
|
|
|
|
/* Circle shape */
|
|
|
|
local BaseCircle; // properties cx,cy,r
|
|
|
|
// point contained in circle?
|
|
private func BaseCircle_IsPointContained(int x, int y)
|
|
{
|
|
x-=this.cx; y-=this.cy;
|
|
var r=this.r;
|
|
return x*x + y*y <= r*r;
|
|
}
|
|
|
|
// bounding rectangle
|
|
private func BaseCircle_GetBoundingRectangle()
|
|
{
|
|
var r=this.r;
|
|
return new Shape.BaseRectangle { x=this.cx-r, y=this.cy-r, wdt=r*2+1, hgt=r*2+1 };
|
|
}
|
|
|
|
private func BaseCircle_GetRandomPoint(proplist result)
|
|
{
|
|
// Make sure radius circles are weighed equally
|
|
var r2 = this.r * this.r + 1;
|
|
if (r2>0x7fff) // for large numbers, the random function doesn't work
|
|
r2 = Random(0x8000) + Random(r2/0x8000+1) * 0x8000;
|
|
else
|
|
r2 = Random(r2);
|
|
var r = Sqrt(r2), a = Random(360);
|
|
result.x = this.cx + Sin(a, r);
|
|
result.y = this.cy + Cos(a, r);
|
|
return true;
|
|
}
|
|
|
|
public func BaseCircle_GetArea()
|
|
{
|
|
// Is aleady covered by general method, but this direct calculation for a simple circle is faster.
|
|
var area = 0;
|
|
for (var x = 0; x <= this.r; x++)
|
|
for (var y = 1; y <= this.r; y++)
|
|
if (x*x + y*y <= this.r*this.r)
|
|
area++;
|
|
return 4 * area + 1;
|
|
}
|
|
|
|
/** Constructor of circular area.
|
|
@par cx Global center x of circle
|
|
@par cy Global center y of circle
|
|
@par r Circle radius
|
|
@return return a shape proplist representing a filled circle around a point
|
|
*/
|
|
public func Circle(int cx, int cy, int r)
|
|
{
|
|
return new BaseCircle { cx=cx, cy=cy, r=r };
|
|
}
|
|
|
|
|
|
/* Intersection */
|
|
|
|
local BaseIntersection;
|
|
|
|
private func BaseIntersection_IsPointContained(int x, int y)
|
|
{
|
|
// Intersection: If any of the sub-areas exclude the point, then it's excluded
|
|
for (var area in this.areas)
|
|
if (!area->IsPointContained(x, y))
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
private func BaseIntersection_GetBoundingRectangle()
|
|
{
|
|
// Bounding rectangle of intersection
|
|
var result, rt;
|
|
for (var area in this.areas)
|
|
if (rt = area->GetBoundingRectangle())
|
|
{
|
|
// first bounds determine area
|
|
if (!result)
|
|
{
|
|
result = new Shape.BaseRectangle {x = rt.x, y = rt.y, wdt = rt.wdt, hgt = rt.hgt};
|
|
}
|
|
else
|
|
{
|
|
// following bounds reduce area
|
|
if (rt.x + rt.wdt < result.x + result.wdt) result.wdt = Max(rt.x + rt.wdt - result.x);
|
|
if (rt.y + rt.hgt < result.y + result.hgt) result.hgt = Max(rt.y + rt.hgt - result.y);
|
|
if (rt.x > result.x)
|
|
{
|
|
result.wdt = Max(result.wdt - rt.x + result.x);
|
|
result.x = rt.x;
|
|
}
|
|
if (rt.y > result.y)
|
|
{
|
|
result.hgt = Max(result.hgt - rt.y + result.y);
|
|
result.y = rt.y;
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
private func BaseIntersection_GetRandomPoint(proplist result, int num_tries)
|
|
{
|
|
// The precise shape of the intersection is unknown. So try 100 times to get a location from any of the subsections
|
|
if (!GetLength(this.areas)) return false;
|
|
if (!num_tries) num_tries = 200;
|
|
while (num_tries>0)
|
|
{
|
|
for (var area in this.areas)
|
|
{
|
|
// get point from subsection
|
|
if (!area->GetRandomPoint(result, num_tries)) return false; // sub-area empty? (note num-tries goes down)
|
|
var pt_ok = true;
|
|
// ensure it's contained in all other subsections
|
|
for (var area2 in this.areas) if (area != area2)
|
|
if (!area2->IsPointContained(result.x, result.y))
|
|
{
|
|
pt_ok = false;
|
|
break;
|
|
}
|
|
if (pt_ok) return true;
|
|
--num_tries;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/** Constructor of intersection area.
|
|
@par c1, c2, ... Up to ten parameters for intersected areas.
|
|
@return return a shape proplist representing a shape that contains only the points included in all the passed sub-shapes.
|
|
*/
|
|
public func Intersect(proplist c1, proplist c2, ...)
|
|
{
|
|
// Intersection of one area?
|
|
if (!c2) return c1;
|
|
// Otherwise, built array
|
|
var areas = [c1, c2], i=1, area;
|
|
while (area = Par(++i)) areas[i] = area;
|
|
return new BaseIntersection { areas = areas };
|
|
}
|
|
|
|
|
|
/* Combination */
|
|
|
|
local BaseCombination;
|
|
|
|
private func BaseCombination_IsPointContained(int x, int y)
|
|
{
|
|
// Combination: If any of the sub-areas include the point, then it's included
|
|
for (var area in this.areas)
|
|
if (area->IsPointContained(x, y))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
private func BaseCombination_GetBoundingRectangle()
|
|
{
|
|
// Bounding rectangle of combination
|
|
var result, rt;
|
|
for (var area in this.areas)
|
|
if (rt = area->GetBoundingRectangle())
|
|
{
|
|
// first bounds determine area
|
|
if (!result)
|
|
{
|
|
result = new Shape.BaseRectangle {x = rt.x, y = rt.y, wdt = rt.wdt, hgt = rt.hgt};
|
|
}
|
|
else
|
|
{
|
|
// following bounds enlarge area
|
|
if (rt.x + rt.wdt > result.x + result.wdt) result.wdt = rt.x + rt.wdt - result.x;
|
|
if (rt.y + rt.hgt > result.y + result.hgt) result.hgt = rt.y + rt.hgt - result.y;
|
|
if (rt.x < result.x)
|
|
{
|
|
result.wdt += result.x - rt.x;
|
|
result.x = rt.x;
|
|
}
|
|
if (rt.y < result.y)
|
|
{
|
|
result.hgt += result.y - rt.y;
|
|
result.y = rt.y;
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
private func BaseCombination_GetRandomPoint(proplist result, int num_tries)
|
|
{
|
|
// Just get a random point from a random subsection
|
|
// Note that this doesn't weigh areas equally.
|
|
var len = GetLength(this.areas);
|
|
if (!len) return false;
|
|
return this.areas[Random(len)]->GetRandomPoint(result, num_tries);
|
|
}
|
|
|
|
/** Constructor of combined area.
|
|
@par c1, c2, ... Up to ten parameters for combined areas.
|
|
@return return a shape proplist representing a shape that contains all the points included in any of the passed sub-shapes.
|
|
*/
|
|
public func Combine(proplist c1, proplist c2, ...)
|
|
{
|
|
// Combination of one area?
|
|
if (!c2) return c1;
|
|
// Otherwise, built array
|
|
var areas = [c1, c2], i=1, area;
|
|
while (area = Par(++i)) areas[i] = area;
|
|
return new BaseCombination { areas = areas };
|
|
}
|
|
|
|
|
|
/* Subtraction */
|
|
|
|
local BaseSubtraction;
|
|
|
|
private func BaseSubtraction_IsPointContained(int x, int y)
|
|
{
|
|
// Subtraction contains everything in "in" area that is not contained in "ex" area
|
|
return this.in->IsPointContained(x, y) && !this.ex->IsPointContained(x, y);
|
|
}
|
|
|
|
private func BaseSubtraction_GetBoundingRectangle()
|
|
{
|
|
// Simply use "in" area since it bounds everything
|
|
return this.in->GetBoundingRectangle();
|
|
}
|
|
|
|
private func BaseSubtraction_GetRandomPoint(proplist result, int num_tries)
|
|
{
|
|
// Get random point in in-area until it lies outside ex-area
|
|
if (!num_tries) num_tries = 200;
|
|
while (num_tries>0)
|
|
{
|
|
if (!this.in->GetRandomPoint(result, num_tries)) return false; // sub-area empty? (note num-tries goes down)
|
|
if (!this.ex->IsPointContained(result.x, result.y)) return true;
|
|
--num_tries;
|
|
}
|
|
// Failed to find a point
|
|
return false;
|
|
}
|
|
|
|
/** Constructor of sutraction area.
|
|
@par in Shape that is included.
|
|
@par ex Shape that is excluded.
|
|
@return return a shape proplist representing a shape that includes the "in" shape and excludes the "ex" shape.
|
|
*/
|
|
public func Subtract(proplist in, proplist ex)
|
|
{
|
|
// Create a new subtraction area
|
|
return new BaseSubtraction { in = in, ex = ex };
|
|
}
|
|
|
|
|
|
|
|
/* Library initialization */
|
|
|
|
public func Definition(def)
|
|
{
|
|
// Initialize function pointers in shape classes
|
|
BaseShape =
|
|
{
|
|
GetArea = Shape.GetArea
|
|
};
|
|
BaseRectangle = new BaseShape
|
|
{
|
|
Type = "rect",
|
|
IsPointContained = Shape.BaseRectangle_IsPointContained,
|
|
GetBoundingRectangle = Shape.BaseRectangle_GetBoundingRectangle,
|
|
GetRandomPoint = Shape.BaseRectangle_GetRandomPoint,
|
|
GetArea = Shape.BaseRectangle_GetArea,
|
|
Find_In = Shape.BaseRectangle_Find_In,
|
|
Find_At = Shape.BaseRectangle_Find_At,
|
|
ToString = Shape.BaseRectangle_ToString,
|
|
IsFullMap = Shape.BaseRectangle_IsFullMap
|
|
};
|
|
BaseCircle = new BaseShape
|
|
{
|
|
IsPointContained = Shape.BaseCircle_IsPointContained,
|
|
GetBoundingRectangle = Shape.BaseCircle_GetBoundingRectangle,
|
|
GetRandomPoint = Shape.BaseCircle_GetRandomPoint,
|
|
GetArea = Shape.BaseCircle_GetArea
|
|
};
|
|
BaseIntersection = new BaseShape
|
|
{
|
|
IsPointContained = Shape.BaseIntersection_IsPointContained,
|
|
GetBoundingRectangle = Shape.BaseIntersection_GetBoundingRectangle,
|
|
GetRandomPoint = Shape.BaseIntersection_GetRandomPoint,
|
|
};
|
|
BaseCombination = new BaseShape
|
|
{
|
|
IsPointContained = Shape.BaseCombination_IsPointContained,
|
|
GetBoundingRectangle = Shape.BaseCombination_GetBoundingRectangle,
|
|
GetRandomPoint = Shape.BaseCombination_GetRandomPoint,
|
|
};
|
|
BaseSubtraction = new BaseShape
|
|
{
|
|
IsPointContained = Shape.BaseSubtraction_IsPointContained,
|
|
GetBoundingRectangle = Shape.BaseSubtraction_GetBoundingRectangle,
|
|
GetRandomPoint = Shape.BaseSubtraction_GetRandomPoint,
|
|
};
|
|
// shape class identity
|
|
BaseRectangle.shape = BaseRectangle;
|
|
BaseCircle.shape = BaseCircle;
|
|
BaseIntersection.shape = BaseIntersection;
|
|
BaseCombination.shape = BaseCombination;
|
|
BaseSubtraction.shape = BaseSubtraction;
|
|
return true;
|
|
}
|
|
|
|
/** Full landscape rectangle
|
|
@return Return a shape proplist representing the rectangle covering the whole current landscape.
|
|
*/
|
|
public func LandscapeRectangle()
|
|
{
|
|
return Rectangle(0, 0, LandscapeWidth(), LandscapeHeight());
|
|
}
|
|
|
|
|
|
/** Constructor of rectangle area. (x,y) is included; (x+w,y+h) is excluded. Automatically flips rectangles of negative size in any dimension.
|
|
@par x Global left side of rectangle
|
|
@par y Global top side of rectangle
|
|
@par w Rectangle width
|
|
@par h Rectangle height
|
|
@return return a shape proplist representing a rectangle
|
|
*/
|
|
global func Rectangle(int x2, int y2, int w2, int h2)
|
|
{
|
|
// normalize
|
|
if(w2 < 0)
|
|
{
|
|
x2 += w2;
|
|
w2 = -w2;
|
|
}
|
|
if(h2 < 0)
|
|
{
|
|
y2 += h2;
|
|
h2 = - h2;
|
|
}
|
|
return new Shape.BaseRectangle {x = x2, y = y2, wdt = w2, hgt = h2};
|
|
}
|