mirror of
https://github.com/RandomityGuy/MBHaxe.git
synced 2025-10-30 08:11:25 +00:00
904 lines
25 KiB
Haxe
904 lines
25 KiB
Haxe
package gui;
|
|
|
|
import h2d.RenderContext;
|
|
import h2d.impl.BatchDrawState;
|
|
import hxd.Math;
|
|
import hxd.impl.Allocator;
|
|
import h2d.Drawable;
|
|
|
|
private typedef GraphicsPoint = hxd.poly2tri.Point;
|
|
|
|
@:dox(hide)
|
|
class GPoint {
|
|
public var x:Float;
|
|
public var y:Float;
|
|
public var r:Float;
|
|
public var g:Float;
|
|
public var b:Float;
|
|
public var a:Float;
|
|
|
|
public function new() {}
|
|
|
|
public function load(x, y, r, g, b, a) {
|
|
this.x = x;
|
|
this.y = y;
|
|
this.r = r;
|
|
this.g = g;
|
|
this.b = b;
|
|
this.a = a;
|
|
}
|
|
}
|
|
|
|
private class GraphicsContent extends h3d.prim.Primitive {
|
|
var tmp:hxd.FloatBuffer;
|
|
var index:hxd.IndexBuffer;
|
|
var state:BatchDrawState;
|
|
|
|
var bufferDirty:Bool;
|
|
var indexDirty:Bool;
|
|
#if track_alloc
|
|
var allocPos:hxd.impl.AllocPos;
|
|
#end
|
|
|
|
var bufferSize:Int;
|
|
var ibufferSize:Int;
|
|
|
|
public function new() {
|
|
state = new BatchDrawState();
|
|
#if track_alloc
|
|
this.allocPos = new hxd.impl.AllocPos();
|
|
#end
|
|
}
|
|
|
|
public inline function addIndex(i) {
|
|
index.push(i);
|
|
state.add(1);
|
|
indexDirty = true;
|
|
}
|
|
|
|
public inline function add(x:Float, y:Float, u:Float, v:Float, r:Float, g:Float, b:Float, a:Float) {
|
|
tmp.push(x);
|
|
tmp.push(y);
|
|
tmp.push(u);
|
|
tmp.push(v);
|
|
tmp.push(r);
|
|
tmp.push(g);
|
|
tmp.push(b);
|
|
tmp.push(a);
|
|
bufferDirty = true;
|
|
}
|
|
|
|
public function setTile(tile:h2d.Tile) {
|
|
state.setTile(tile);
|
|
}
|
|
|
|
public function next() {
|
|
var nvect = tmp.length >> 3;
|
|
if (nvect < 1 << 15)
|
|
return false;
|
|
tmp = new hxd.FloatBuffer();
|
|
index = new hxd.IndexBuffer();
|
|
var tex = state.currentTexture;
|
|
state = new BatchDrawState();
|
|
state.setTexture(tex);
|
|
super.dispose();
|
|
return true;
|
|
}
|
|
|
|
override function alloc(engine:h3d.Engine) {
|
|
if (index.length <= 0)
|
|
return;
|
|
var alloc = Allocator.get();
|
|
buffer = alloc.ofFloats(tmp, 8, RawFormat);
|
|
bufferSize = tmp.length;
|
|
#if track_alloc
|
|
@:privateAccess buffer.allocPos = allocPos;
|
|
#end
|
|
indexes = alloc.ofIndexes(index);
|
|
ibufferSize = index.length;
|
|
bufferDirty = false;
|
|
indexDirty = false;
|
|
}
|
|
|
|
public function doRender(ctx:h2d.RenderContext) {
|
|
if (index.length == 0)
|
|
return;
|
|
flush();
|
|
state.drawIndexed(ctx, buffer, indexes, 0, tmp.length >> 3);
|
|
}
|
|
|
|
public function flush() {
|
|
if (buffer == null || buffer.isDisposed()) {
|
|
alloc(h3d.Engine.getCurrent());
|
|
} else {
|
|
var allocator = Allocator.get();
|
|
if (bufferDirty) {
|
|
if (tmp.length > bufferSize) {
|
|
allocator.disposeBuffer(buffer);
|
|
|
|
buffer = new h3d.Buffer(tmp.length >> 3, 8, [RawFormat, Dynamic]);
|
|
buffer.uploadVector(tmp, 0, tmp.length >> 3);
|
|
bufferSize = tmp.length;
|
|
} else {
|
|
buffer.uploadVector(tmp, 0, tmp.length >> 3);
|
|
}
|
|
bufferDirty = false;
|
|
}
|
|
if (indexDirty) {
|
|
if (index.length > ibufferSize) {
|
|
allocator.disposeIndexBuffer(indexes);
|
|
indexes = allocator.ofIndexes(index);
|
|
ibufferSize = index.length;
|
|
} else {
|
|
indexes.upload(index, 0, index.length);
|
|
}
|
|
indexDirty = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
override function dispose() {
|
|
state.clear();
|
|
// disposeBuffers();
|
|
|
|
// super.dispose();
|
|
}
|
|
|
|
function disposeBuffers() {
|
|
if (buffer != null) {
|
|
Allocator.get().disposeBuffer(buffer);
|
|
buffer = null;
|
|
}
|
|
if (indexes != null) {
|
|
Allocator.get().disposeIndexBuffer(indexes);
|
|
indexes = null;
|
|
}
|
|
}
|
|
|
|
public function clear() {
|
|
dispose();
|
|
tmp = new hxd.FloatBuffer();
|
|
index = new hxd.IndexBuffer();
|
|
}
|
|
|
|
public function disposeForReal() {
|
|
state.clear();
|
|
disposeBuffers();
|
|
super.dispose();
|
|
}
|
|
}
|
|
|
|
/**
|
|
A simple interface to draw arbitrary 2D geometry.
|
|
|
|
Usage notes:
|
|
* While Graphics allows for multiple unique textures, each texture swap causes a new drawcall,
|
|
and due to that it's recommended to minimize the amount of used textures per Graphics instance,
|
|
ideally limiting to only one texture.
|
|
* Due to how Graphics operate, removing them from the active `h2d.Scene` will cause a loss of all data.
|
|
**/
|
|
class Graphics extends Drawable {
|
|
var content:GraphicsContent;
|
|
var tmpPoints:Array<GPoint>;
|
|
var pindex:Int;
|
|
var curR:Float;
|
|
var curG:Float;
|
|
var curB:Float;
|
|
var curA:Float;
|
|
var lineSize:Float;
|
|
var lineR:Float;
|
|
var lineG:Float;
|
|
var lineB:Float;
|
|
var lineA:Float;
|
|
var doFill:Bool;
|
|
|
|
var xMin:Float;
|
|
var yMin:Float;
|
|
var xMax:Float;
|
|
var yMax:Float;
|
|
var xMinSize:Float;
|
|
var yMinSize:Float;
|
|
var xMaxSize:Float;
|
|
var yMaxSize:Float;
|
|
|
|
var ma:Float = 1.;
|
|
var mb:Float = 0.;
|
|
var mc:Float = 0.;
|
|
var md:Float = 1.;
|
|
var mx:Float = 0.;
|
|
var my:Float = 0.;
|
|
|
|
/**
|
|
The Tile used as source of Texture to render.
|
|
**/
|
|
public var tile:h2d.Tile;
|
|
|
|
/**
|
|
Adds bevel cut-off at line corners.
|
|
|
|
The value is a percentile in range of 0...1, dictating at which point edges get beveled based on their angle.
|
|
Value of 0 being not beveled and 1 being always beveled.
|
|
**/
|
|
public var bevel = 0.25; // 0 = not beveled, 1 = always beveled
|
|
|
|
/**
|
|
Create a new Graphics instance.
|
|
@param parent An optional parent `h2d.Object` instance to which Graphics adds itself if set.
|
|
**/
|
|
public function new(?parent) {
|
|
super(parent);
|
|
content = new GraphicsContent();
|
|
tile = h2d.Tile.fromColor(0xFFFFFF);
|
|
clear();
|
|
}
|
|
|
|
override function onRemove() {
|
|
super.onRemove();
|
|
clear();
|
|
content.disposeForReal();
|
|
}
|
|
|
|
/**
|
|
Clears the Graphics contents.
|
|
**/
|
|
public function clear() {
|
|
content.clear();
|
|
tmpPoints = [];
|
|
pindex = 0;
|
|
lineSize = 0;
|
|
xMin = Math.POSITIVE_INFINITY;
|
|
yMin = Math.POSITIVE_INFINITY;
|
|
yMax = Math.NEGATIVE_INFINITY;
|
|
xMax = Math.NEGATIVE_INFINITY;
|
|
xMinSize = Math.POSITIVE_INFINITY;
|
|
yMinSize = Math.POSITIVE_INFINITY;
|
|
yMaxSize = Math.NEGATIVE_INFINITY;
|
|
xMaxSize = Math.NEGATIVE_INFINITY;
|
|
}
|
|
|
|
override function getBoundsRec(relativeTo, out, forSize) {
|
|
super.getBoundsRec(relativeTo, out, forSize);
|
|
if (tile != null) {
|
|
if (forSize)
|
|
addBounds(relativeTo, out, xMinSize, yMinSize, xMaxSize - xMinSize, yMaxSize - yMinSize);
|
|
else
|
|
addBounds(relativeTo, out, xMin, yMin, xMax - xMin, yMax - yMin);
|
|
}
|
|
}
|
|
|
|
function isConvex(points:Array<GPoint>) {
|
|
var first = true, sign = false;
|
|
for (i in 0...points.length) {
|
|
var p1 = points[i];
|
|
var p2 = points[(i + 1) % points.length];
|
|
var p3 = points[(i + 2) % points.length];
|
|
var s = (p2.x - p1.x) * (p3.y - p1.y) - (p2.y - p1.y) * (p3.x - p1.x) > 0;
|
|
if (first) {
|
|
first = false;
|
|
sign = s;
|
|
} else if (sign != s)
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function flushLine(start) {
|
|
var pts = tmpPoints;
|
|
var last = pts.length - 1;
|
|
var prev = pts[last];
|
|
var p = pts[0];
|
|
|
|
content.setTile(h2d.Tile.fromColor(0xFFFFFF));
|
|
var closed = p.x == prev.x && p.y == prev.y;
|
|
var count = pts.length;
|
|
if (!closed) {
|
|
var prevLast = pts[last - 1];
|
|
if (prevLast == null)
|
|
prevLast = p;
|
|
var gp = new GPoint();
|
|
gp.load(prev.x * 2 - prevLast.x, prev.y * 2 - prevLast.y, 0, 0, 0, 0);
|
|
pts.push(gp);
|
|
var pNext = pts[1];
|
|
if (pNext == null)
|
|
pNext = p;
|
|
var gp = new GPoint();
|
|
gp.load(p.x * 2 - pNext.x, p.y * 2 - pNext.y, 0, 0, 0, 0);
|
|
prev = gp;
|
|
} else if (p != prev) {
|
|
count--;
|
|
last--;
|
|
prev = pts[last];
|
|
}
|
|
|
|
for (i in 0...count) {
|
|
var next = pts[(i + 1) % pts.length];
|
|
|
|
var nx1 = prev.y - p.y;
|
|
var ny1 = p.x - prev.x;
|
|
var ns1 = Math.invSqrt(nx1 * nx1 + ny1 * ny1);
|
|
|
|
var nx2 = p.y - next.y;
|
|
var ny2 = next.x - p.x;
|
|
var ns2 = Math.invSqrt(nx2 * nx2 + ny2 * ny2);
|
|
|
|
var nx = nx1 * ns1 + nx2 * ns2;
|
|
var ny = ny1 * ns1 + ny2 * ns2;
|
|
var ns = Math.invSqrt(nx * nx + ny * ny);
|
|
|
|
nx *= ns;
|
|
ny *= ns;
|
|
|
|
var size = nx * nx1 * ns1 + ny * ny1 * ns1; // N.N1
|
|
|
|
// *HACK* we should instead properly detect limits when the angle is too small
|
|
if (size < 0.1)
|
|
size = 0.1;
|
|
|
|
var d = lineSize * 0.5 / size;
|
|
nx *= d;
|
|
ny *= d;
|
|
|
|
if (size > bevel) {
|
|
content.add(p.x + nx, p.y + ny, 0, 0, p.r, p.g, p.b, p.a);
|
|
content.add(p.x - nx, p.y - ny, 0, 0, p.r, p.g, p.b, p.a);
|
|
|
|
var pnext = i == last ? start : pindex + 2;
|
|
|
|
if (i < count - 1 || closed) {
|
|
content.addIndex(pindex);
|
|
content.addIndex(pindex + 1);
|
|
content.addIndex(pnext);
|
|
|
|
content.addIndex(pindex + 1);
|
|
content.addIndex(pnext);
|
|
content.addIndex(pnext + 1);
|
|
}
|
|
pindex += 2;
|
|
} else {
|
|
// bevel
|
|
var n0x = next.x - p.x;
|
|
var n0y = next.y - p.y;
|
|
var sign = n0x * nx + n0y * ny;
|
|
|
|
var nnx = -ny;
|
|
var nny = nx;
|
|
|
|
var size = nnx * nx1 * ns1 + nny * ny1 * ns1;
|
|
var d = lineSize * 0.5 / size;
|
|
nnx *= d;
|
|
nny *= d;
|
|
|
|
var pnext = i == last ? start : pindex + 3;
|
|
|
|
if (sign > 0) {
|
|
content.add(p.x + nx, p.y + ny, 0, 0, p.r, p.g, p.b, p.a);
|
|
content.add(p.x - nnx, p.y - nny, 0, 0, p.r, p.g, p.b, p.a);
|
|
content.add(p.x + nnx, p.y + nny, 0, 0, p.r, p.g, p.b, p.a);
|
|
|
|
content.addIndex(pindex);
|
|
content.addIndex(pnext);
|
|
content.addIndex(pindex + 2);
|
|
|
|
content.addIndex(pindex + 2);
|
|
content.addIndex(pnext);
|
|
content.addIndex(pnext + 1);
|
|
} else {
|
|
content.add(p.x + nnx, p.y + nny, 0, 0, p.r, p.g, p.b, p.a);
|
|
content.add(p.x - nx, p.y - ny, 0, 0, p.r, p.g, p.b, p.a);
|
|
content.add(p.x - nnx, p.y - nny, 0, 0, p.r, p.g, p.b, p.a);
|
|
|
|
content.addIndex(pindex + 1);
|
|
content.addIndex(pnext);
|
|
content.addIndex(pindex + 2);
|
|
|
|
content.addIndex(pindex + 1);
|
|
content.addIndex(pnext);
|
|
content.addIndex(pnext + 1);
|
|
}
|
|
|
|
content.addIndex(pindex);
|
|
content.addIndex(pindex + 1);
|
|
content.addIndex(pindex + 2);
|
|
|
|
pindex += 3;
|
|
}
|
|
|
|
prev = p;
|
|
p = next;
|
|
}
|
|
content.setTile(tile);
|
|
}
|
|
|
|
static var EARCUT = null;
|
|
|
|
function flushFill(i0) {
|
|
if (tmpPoints.length < 3)
|
|
return;
|
|
|
|
var pts = tmpPoints;
|
|
var p0 = pts[0];
|
|
var p1 = pts[pts.length - 1];
|
|
var last = null;
|
|
// closed poly
|
|
if (hxd.Math.abs(p0.x - p1.x) < 1e-9 && hxd.Math.abs(p0.y - p1.y) < 1e-9)
|
|
last = pts.pop();
|
|
|
|
if (isConvex(pts)) {
|
|
for (i in 1...pts.length - 1) {
|
|
content.addIndex(i0);
|
|
content.addIndex(i0 + i);
|
|
content.addIndex(i0 + i + 1);
|
|
}
|
|
} else {
|
|
var ear = EARCUT;
|
|
if (ear == null)
|
|
EARCUT = ear = new hxd.earcut.Earcut();
|
|
for (i in ear.triangulate(pts))
|
|
content.addIndex(i + i0);
|
|
}
|
|
|
|
if (last != null)
|
|
pts.push(last);
|
|
}
|
|
|
|
function flush() {
|
|
if (tmpPoints.length == 0)
|
|
return;
|
|
if (doFill) {
|
|
flushFill(pindex);
|
|
pindex += tmpPoints.length;
|
|
if (content.next())
|
|
pindex = 0;
|
|
}
|
|
if (lineSize > 0) {
|
|
flushLine(pindex);
|
|
if (content.next())
|
|
pindex = 0;
|
|
}
|
|
tmpPoints = [];
|
|
}
|
|
|
|
/**
|
|
Begins a solid color fill.
|
|
|
|
Beginning new fill will finish previous fill operation without need to call `Graphics.endFill`.
|
|
|
|
@param color An RGB color with which to fill the drawn shapes.
|
|
@param alpha A transparency of the fill color.
|
|
**/
|
|
public function beginFill(color:Int = 0, alpha = 1.) {
|
|
flush();
|
|
tile = h2d.Tile.fromColor(0xFFFFFF);
|
|
content.setTile(tile);
|
|
setColor(color, alpha);
|
|
doFill = true;
|
|
}
|
|
|
|
/**
|
|
Position a virtual tile at the given position and scale. Every draw will display a part of this tile relative
|
|
to these coordinates.
|
|
|
|
Note that in by default, Tile is not wrapped, and in order to render tiling texture, `Drawable.tileWrap` have to be set.
|
|
Additionally, both `Tile.dx` and `Tile.dy` are ignored (use `dx`/`dy` arguments instead)
|
|
as well as tile defined size of the tile through `Tile.width` and `Tile.height` (use `scaleX`/`scaleY` relative to texture size).
|
|
|
|
Beginning new fill will finish previous fill operation without need to call `Graphics.endFill`.
|
|
|
|
@param dx An X offset of the Tile relative to Graphics.
|
|
@param dy An Y offset of the Tile relative to Graphics.
|
|
@param scaleX A horizontal scale factor applied to the Tile texture.
|
|
@param scaleY A vertical scale factor applied to the Tile texture.
|
|
@param tile The tile to fill with. If null, uses previously used Tile with `beginTileFill` or throws an error.
|
|
Previous tile is remembered across `Graphics.clear` calls.
|
|
**/
|
|
public function beginTileFill(?dx:Float, ?dy:Float, ?scaleX:Float, ?scaleY:Float, ?tile:h2d.Tile) {
|
|
if (tile == null)
|
|
tile = this.tile;
|
|
if (tile == null)
|
|
throw "Tile not specified";
|
|
flush();
|
|
this.tile = tile;
|
|
content.setTile(tile);
|
|
setColor(0xFFFFFF);
|
|
doFill = true;
|
|
|
|
if (dx == null)
|
|
dx = 0;
|
|
if (dy == null)
|
|
dy = 0;
|
|
if (scaleX == null)
|
|
scaleX = 1;
|
|
if (scaleY == null)
|
|
scaleY = 1;
|
|
dx -= tile.x;
|
|
dy -= tile.y;
|
|
|
|
var tex = tile.getTexture();
|
|
var pixWidth = 1 / tex.width;
|
|
var pixHeight = 1 / tex.height;
|
|
ma = pixWidth / scaleX;
|
|
mb = 0;
|
|
mc = 0;
|
|
md = pixHeight / scaleY;
|
|
mx = -dx * ma;
|
|
my = -dy * md;
|
|
}
|
|
|
|
/**
|
|
Draws a Tile at given position.
|
|
See `Graphics.beginTileFill` for limitations.
|
|
|
|
This methods ends current fill operation.
|
|
@param x The X position of the tile.
|
|
@param y The Y position of the tile.
|
|
@param tile The tile to draw.
|
|
**/
|
|
public function drawTile(x:Float, y:Float, tile:h2d.Tile) {
|
|
beginTileFill(x, y, tile);
|
|
drawRect(x, y, tile.width, tile.height);
|
|
endFill();
|
|
}
|
|
|
|
/**
|
|
Sets an outline style. Changing the line style ends the currently drawn line.
|
|
|
|
@param size Width of the outline. Setting size to 0 will remove the outline.
|
|
@param color An outline RGB color.
|
|
@param alpha An outline transparency.
|
|
**/
|
|
public function lineStyle(size:Float = 0, color = 0, alpha = 1.) {
|
|
flush();
|
|
this.lineSize = size;
|
|
lineA = alpha;
|
|
lineR = ((color >> 16) & 0xFF) / 255.;
|
|
lineG = ((color >> 8) & 0xFF) / 255.;
|
|
lineB = (color & 0xFF) / 255.;
|
|
}
|
|
|
|
/**
|
|
Ends the current line and starts new one at given position.
|
|
**/
|
|
public inline function moveTo(x, y) {
|
|
flush();
|
|
lineTo(x, y);
|
|
}
|
|
|
|
/**
|
|
Ends the current fill operation.
|
|
**/
|
|
public function endFill() {
|
|
flush();
|
|
doFill = false;
|
|
}
|
|
|
|
/**
|
|
Changes current fill color.
|
|
Does not interrupt current fill operation and can be utilized to customize color per vertex.
|
|
During tile fill operation, color serves as a tile color multiplier.
|
|
@param color The new fill color.
|
|
@param alpha The new fill transparency.
|
|
**/
|
|
public inline function setColor(color:Int, alpha:Float = 1.) {
|
|
curA = alpha;
|
|
curR = ((color >> 16) & 0xFF) / 255.;
|
|
curG = ((color >> 8) & 0xFF) / 255.;
|
|
curB = (color & 0xFF) / 255.;
|
|
}
|
|
|
|
/**
|
|
Draws a rectangle with given parameters.
|
|
@param x The rectangle top-left corner X position.
|
|
@param y The rectangle top-left corner Y position.
|
|
@param w The rectangle width.
|
|
@param h The rectangle height.
|
|
**/
|
|
public function drawRect(x:Float, y:Float, w:Float, h:Float) {
|
|
flush();
|
|
lineTo(x, y);
|
|
lineTo(x + w, y);
|
|
lineTo(x + w, y + h);
|
|
lineTo(x, y + h);
|
|
lineTo(x, y);
|
|
var e = 0.01; // see #776
|
|
tmpPoints[0].x += e;
|
|
tmpPoints[0].y += e;
|
|
tmpPoints[1].y += e;
|
|
tmpPoints[3].x += e;
|
|
tmpPoints[4].x += e;
|
|
tmpPoints[4].y += e;
|
|
flush();
|
|
}
|
|
|
|
/**
|
|
Draws a rounded rectangle with given parameters.
|
|
@param x The rectangle top-left corner X position.
|
|
@param y The rectangle top-left corner Y position.
|
|
@param w The rectangle width.
|
|
@param h The rectangle height.
|
|
@param radius Radius of the rectangle corners.
|
|
@param nsegments Amount of segments used for corners. When `0` segment count calculated automatically.
|
|
**/
|
|
public function drawRoundedRect(x:Float, y:Float, w:Float, h:Float, radius:Float, nsegments = 0) {
|
|
if (radius <= 0) {
|
|
return drawRect(x, y, w, h);
|
|
}
|
|
x += radius;
|
|
y += radius;
|
|
w -= radius * 2;
|
|
h -= radius * 2;
|
|
flush();
|
|
if (nsegments == 0)
|
|
nsegments = Math.ceil(Math.abs(radius * hxd.Math.degToRad(90) / 4));
|
|
if (nsegments < 3)
|
|
nsegments = 3;
|
|
var angle = hxd.Math.degToRad(90) / (nsegments - 1);
|
|
inline function corner(x, y, angleStart) {
|
|
for (i in 0...nsegments) {
|
|
var a = i * angle + hxd.Math.degToRad(angleStart);
|
|
lineTo(x + Math.cos(a) * radius, y + Math.sin(a) * radius);
|
|
}
|
|
}
|
|
lineTo(x, y - radius);
|
|
lineTo(x + w, y - radius);
|
|
corner(x + w, y, 270);
|
|
lineTo(x + w + radius, y + h);
|
|
corner(x + w, y + h, 0);
|
|
lineTo(x, y + h + radius);
|
|
corner(x, y + h, 90);
|
|
lineTo(x - radius, y);
|
|
corner(x, y, 180);
|
|
flush();
|
|
}
|
|
|
|
/**
|
|
Draws a circle centered at given position.
|
|
@param cx X center position of the circle.
|
|
@param cy Y center position of the circle.
|
|
@param radius Radius of the circle.
|
|
@param nsegments Amount of segments used to draw the circle. When `0`, amount of segments calculated automatically.
|
|
**/
|
|
public function drawCircle(cx:Float, cy:Float, radius:Float, nsegments = 0) {
|
|
flush();
|
|
if (nsegments == 0)
|
|
nsegments = Math.ceil(Math.abs(radius * 3.14 * 2 / 4));
|
|
if (nsegments < 3)
|
|
nsegments = 3;
|
|
var angle = Math.PI * 2 / nsegments;
|
|
for (i in 0...nsegments + 1) {
|
|
var a = i * angle;
|
|
lineTo(cx + Math.cos(a) * radius, cy + Math.sin(a) * radius);
|
|
}
|
|
flush();
|
|
}
|
|
|
|
/**
|
|
Draws an ellipse centered at given position.
|
|
@param cx X center position of the ellipse.
|
|
@param cy Y center position of the ellipse.
|
|
@param radiusX Horizontal radius of an ellipse.
|
|
@param radiusY Vertical radius of an ellipse.
|
|
@param rotationAngle Ellipse rotation in radians.
|
|
@param nsegments Amount of segments used to draw an ellipse. When `0`, amount of segments calculated automatically.
|
|
**/
|
|
public function drawEllipse(cx:Float, cy:Float, radiusX:Float, radiusY:Float, rotationAngle:Float = 0, nsegments = 0) {
|
|
flush();
|
|
if (nsegments == 0)
|
|
nsegments = Math.ceil(Math.abs(radiusY * 3.14 * 2 / 4));
|
|
if (nsegments < 3)
|
|
nsegments = 3;
|
|
var angle = Math.PI * 2 / nsegments;
|
|
var x1, y1;
|
|
for (i in 0...nsegments + 1) {
|
|
var a = i * angle;
|
|
x1 = Math.cos(a) * Math.cos(rotationAngle) * radiusX - Math.sin(a) * Math.sin(rotationAngle) * radiusY;
|
|
y1 = Math.cos(rotationAngle) * Math.sin(a) * radiusY + Math.cos(a) * Math.sin(rotationAngle) * radiusX;
|
|
lineTo(cx + x1, cy + y1);
|
|
}
|
|
flush();
|
|
}
|
|
|
|
/**
|
|
Draws a pie centered at given position.
|
|
@param cx X center position of the pie.
|
|
@param cy Y center position of the pie.
|
|
@param radius Radius of the pie.
|
|
@param angleStart Starting angle of the pie in radians.
|
|
@param angleLength The pie size in clockwise direction with `2*PI` being full circle.
|
|
@param nsegments Amount of segments used to draw the pie. When `0`, amount of segments calculated automatically.
|
|
**/
|
|
public function drawPie(cx:Float, cy:Float, radius:Float, angleStart:Float, angleLength:Float, nsegments = 0) {
|
|
if (Math.abs(angleLength) >= Math.PI * 2) {
|
|
return drawCircle(cx, cy, radius, nsegments);
|
|
}
|
|
flush();
|
|
lineTo(cx, cy);
|
|
if (nsegments == 0)
|
|
nsegments = Math.ceil(Math.abs(radius * angleLength / 4));
|
|
if (nsegments < 3)
|
|
nsegments = 3;
|
|
var angle = angleLength / (nsegments - 1);
|
|
for (i in 0...nsegments) {
|
|
var a = i * angle + angleStart;
|
|
lineTo(cx + Math.cos(a) * radius, cy + Math.sin(a) * radius);
|
|
}
|
|
lineTo(cx, cy);
|
|
flush();
|
|
}
|
|
|
|
/**
|
|
Draws a double-edged pie centered at given position.
|
|
@param cx X center position of the pie.
|
|
@param cy Y center position of the pie.
|
|
@param radius The outer radius of the pie.
|
|
@param innerRadius The inner radius of the pie.
|
|
@param angleStart Starting angle of the pie in radians.
|
|
@param angleLength The pie size in clockwise direction with `2*PI` being full circle.
|
|
@param nsegments Amount of segments used to draw the pie. When `0`, amount of segments calculated automatically.
|
|
**/
|
|
public function drawPieInner(cx:Float, cy:Float, radius:Float, innerRadius:Float, angleStart:Float, angleLength:Float, nsegments = 0) {
|
|
flush();
|
|
if (Math.abs(angleLength) >= Math.PI * 2 + 1e-3)
|
|
angleLength = Math.PI * 2 + 1e-3;
|
|
|
|
var cs = Math.cos(angleStart);
|
|
var ss = Math.sin(angleStart);
|
|
var ce = Math.cos(angleStart + angleLength);
|
|
var se = Math.sin(angleStart + angleLength);
|
|
|
|
lineTo(cx + cs * innerRadius, cy + ss * innerRadius);
|
|
|
|
if (nsegments == 0)
|
|
nsegments = Math.ceil(Math.abs(radius * angleLength / 4));
|
|
if (nsegments < 3)
|
|
nsegments = 3;
|
|
var angle = angleLength / (nsegments - 1);
|
|
for (i in 0...nsegments) {
|
|
var a = i * angle + angleStart;
|
|
lineTo(cx + Math.cos(a) * radius, cy + Math.sin(a) * radius);
|
|
}
|
|
lineTo(cx + ce * innerRadius, cy + se * innerRadius);
|
|
for (i in 0...nsegments) {
|
|
var a = (nsegments - 1 - i) * angle + angleStart;
|
|
lineTo(cx + Math.cos(a) * innerRadius, cy + Math.sin(a) * innerRadius);
|
|
}
|
|
flush();
|
|
}
|
|
|
|
/**
|
|
Draws a rectangular pie centered at given position.
|
|
@param cx X center position of the pie.
|
|
@param cy Y center position of the pie.
|
|
@param width Width of the pie.
|
|
@param height Height of the pie.
|
|
@param angleStart Starting angle of the pie in radians.
|
|
@param angleLength The pie size in clockwise direction with `2*PI` being solid rectangle.
|
|
@param nsegments Amount of segments used to draw the pie. When `0`, amount of segments calculated automatically.
|
|
**/
|
|
public function drawRectanglePie(cx:Float, cy:Float, width:Float, height:Float, angleStart:Float, angleLength:Float, nsegments = 0) {
|
|
if (Math.abs(angleLength) >= Math.PI * 2) {
|
|
return drawRect(cx - (width / 2), cy - (height / 2), width, height);
|
|
}
|
|
flush();
|
|
lineTo(cx, cy);
|
|
if (nsegments == 0)
|
|
nsegments = Math.ceil(Math.abs(Math.max(width, height) * angleLength / 4));
|
|
if (nsegments < 3)
|
|
nsegments = 3;
|
|
var angle = angleLength / (nsegments - 1);
|
|
var square2 = Math.sqrt(2);
|
|
for (i in 0...nsegments) {
|
|
var a = i * angle + angleStart;
|
|
|
|
var _width = Math.cos(a) * (width / 2 + 1) * square2;
|
|
var _height = Math.sin(a) * (height / 2 + 1) * square2;
|
|
|
|
_width = Math.abs(_width) >= width / 2 ? (Math.cos(a) < 0 ? width / 2 * -1 : width / 2) : _width;
|
|
_height = Math.abs(_height) >= height / 2 ? (Math.sin(a) < 0 ? height / 2 * -1 : height / 2) : _height;
|
|
|
|
lineTo(cx + _width, cy + _height);
|
|
}
|
|
lineTo(cx, cy);
|
|
flush();
|
|
}
|
|
|
|
/**
|
|
* Draws a quadratic Bezier curve using the current line style from the current drawing position to (cx, cy) and using the control point that (bx, by) specifies.
|
|
* IvanK Lib port ( http://lib.ivank.net )
|
|
*/
|
|
public function curveTo(bx:Float, by:Float, cx:Float, cy:Float) {
|
|
var ax = tmpPoints.length == 0 ? 0 : tmpPoints[tmpPoints.length - 1].x;
|
|
var ay = tmpPoints.length == 0 ? 0 : tmpPoints[tmpPoints.length - 1].y;
|
|
var t = 2 / 3;
|
|
cubicCurveTo(ax + t * (bx - ax), ay + t * (by - ay), cx + t * (bx - cx), cy + t * (by - cy), cx, cy);
|
|
}
|
|
|
|
/**
|
|
* Draws a cubic Bezier curve from the current drawing position to the specified anchor point.
|
|
* IvanK Lib port ( http://lib.ivank.net )
|
|
* @param bx control X for start point
|
|
* @param by control Y for start point
|
|
* @param cx control X for end point
|
|
* @param cy control Y for end point
|
|
* @param dx end X
|
|
* @param dy end Y
|
|
* @param nsegments = 40
|
|
*/
|
|
public function cubicCurveTo(bx:Float, by:Float, cx:Float, cy:Float, dx:Float, dy:Float, nsegments = 40) {
|
|
var ax = tmpPoints.length == 0 ? 0 : tmpPoints[tmpPoints.length - 1].x;
|
|
var ay = tmpPoints.length == 0 ? 0 : tmpPoints[tmpPoints.length - 1].y;
|
|
var tobx = bx - ax, toby = by - ay;
|
|
var tocx = cx - bx, tocy = cy - by;
|
|
var todx = dx - cx, tody = dy - cy;
|
|
var step = 1 / nsegments;
|
|
|
|
for (i in 1...nsegments) {
|
|
var d = i * step;
|
|
var px = ax + d * tobx, py = ay + d * toby;
|
|
var qx = bx + d * tocx, qy = by + d * tocy;
|
|
var rx = cx + d * todx, ry = cy + d * tody;
|
|
var toqx = qx - px, toqy = qy - py;
|
|
var torx = rx - qx, tory = ry - qy;
|
|
|
|
var sx = px + d * toqx, sy = py + d * toqy;
|
|
var tx = qx + d * torx, ty = qy + d * tory;
|
|
var totx = tx - sx, toty = ty - sy;
|
|
lineTo(sx + d * totx, sy + d * toty);
|
|
}
|
|
lineTo(dx, dy);
|
|
}
|
|
|
|
/**
|
|
Draws a straight line from the current drawing position to the given position.
|
|
**/
|
|
public inline function lineTo(x:Float, y:Float) {
|
|
addVertex(x, y, curR, curG, curB, curA, x * ma + y * mc + mx, x * mb + y * md + my);
|
|
}
|
|
|
|
/**
|
|
Advanced usage. Adds new vertex to the current polygon with given parameters and current line style.
|
|
@param x Vertex X position
|
|
@param y Vertex Y position
|
|
@param r Red tint value of the vertex when performing fill operation.
|
|
@param g Green tint value of the vertex when performing fill operation.
|
|
@param b Blue tint value of the vertex when performing fill operation.
|
|
@param a Alpha of the vertex when performing fill operation.
|
|
@param u Normalized horizontal Texture position from the current Tile fill operation.
|
|
@param v Normalized vertical Texture position from the current Tile fill operation.
|
|
**/
|
|
public function addVertex(x:Float, y:Float, r:Float, g:Float, b:Float, a:Float, u:Float = 0., v:Float = 0.) {
|
|
var half = lineSize / 2.0;
|
|
if (x - half < xMin)
|
|
xMin = x - half;
|
|
if (y - half < yMin)
|
|
yMin = y - half;
|
|
if (x + half > xMax)
|
|
xMax = x + half;
|
|
if (y + half > yMax)
|
|
yMax = y + half;
|
|
if (x < xMinSize)
|
|
xMinSize = x;
|
|
if (y < yMinSize)
|
|
yMinSize = y;
|
|
if (x > xMaxSize)
|
|
xMaxSize = x;
|
|
if (y > yMaxSize)
|
|
yMaxSize = y;
|
|
if (doFill)
|
|
content.add(x, y, u, v, r, g, b, a);
|
|
var gp = new GPoint();
|
|
gp.load(x, y, lineR, lineG, lineB, lineA);
|
|
tmpPoints.push(gp);
|
|
}
|
|
|
|
override function draw(ctx:RenderContext) {
|
|
if (!ctx.beginDrawBatchState(this))
|
|
return;
|
|
content.doRender(ctx);
|
|
}
|
|
|
|
override function sync(ctx:RenderContext) {
|
|
super.sync(ctx);
|
|
flush();
|
|
content.flush();
|
|
}
|
|
}
|