Skip to content

Instantly share code, notes, and snippets.

@notnullnotvoid
Last active June 14, 2019 22:32
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save notnullnotvoid/667bd229cc4ab5c93cbc7b1ae21e23c9 to your computer and use it in GitHub Desktop.
Save notnullnotvoid/667bd229cc4ab5c93cbc7b1ae21e23c9 to your computer and use it in GitHub Desktop.
Newly optimized versions of P2DX methods
//NOTE: here I've included only the methods that I changed.
@Override
public void ellipseImpl(float a, float b, float c, float d) {
if (useParentImpl) {
super.ellipseImpl(a, b, c, d);
return;
}
beginShape(POLYGON);
//convert corner/diameter to center/radius
float rx = c * 0.5f;
float ry = d * 0.5f;
float x = a + rx;
float y = b + ry;
//since very wide stroke and/or very small radius might cause the
//stroke to account for a significant portion of the overall radius,
//we take it into account when calculating detail, just to be safe
int segments = circleDetail(PApplet.max(rx, ry) + (stroke? strokeWeight : 0), TWO_PI);
float step = TWO_PI / segments;
float cos = PApplet.cos(step);
float sin = PApplet.sin(step);
float dx = 0, dy = 1;
for (int i = 0; i < segments; ++i) {
shapeVertex(x + dx * rx, y + dy * ry, 0, 0, fillColor, 0);
//this is the equivalent of multiplying the vector <dx, dy> by the 2x2 rotation matrix [[cos -sin] [sin cos]]
float tempx = dx * cos - dy * sin;
dy = dx * sin + dy * cos;
dx = tempx;
}
knownConvexPolygon = true;
endShape(CLOSE);
}
@Override
protected void arcImpl(float x, float y, float w, float h, float start, float stop, int mode) {
if (useParentImpl) {
super.arcImpl(x, y, w, h, start, stop, mode);
return;
}
//INVARIANT: stop > start
//INVARIANT: stop - start <= TWO_PI
//convert corner/diameter to center/radius
w *= 0.5f;
h *= 0.5f;
x += w;
y += h;
float diff = stop - start;
int segments = circleDetail(PApplet.max(w, h), diff);
float step = diff / segments;
beginShape(POLYGON);
//no constant is defined for the default arc mode, so we just use a literal 0
//(this is consistent with code elsewhere)
if (mode == 0 || mode == PIE) {
vertex(x, y);
}
if (mode == 0) {
//kinda hacky way to disable drawing a stroke along the first edge
appendContour(vertCount);
}
float dx = PApplet.cos(start);
float dy = PApplet.sin(start);
float c = PApplet.cos(step);
float s = PApplet.sin(step);
for (int i = 0; i <= segments; ++i) {
shapeVertex(x + dx * w, y + dy * h, 0, 0, fillColor, 0);
//this is the equivalent of multiplying the vector <dx, dy> by the 2x2 rotation matrix [[c -s] [s c]]
float tempx = dx * c - dy * s;
dy = dx * s + dy * c;
dx = tempx;
}
//for the case `(mode == PIE || mode == 0) && diff > HALF_PI`, the polygon
//will not actually be convex, but due to known vertex order, we can still safely tessellate as if it is
knownConvexPolygon = true;
if (mode == CHORD || mode == PIE) {
endShape(CLOSE);
} else {
endShape();
}
}
void postMatrixChanged() {
//this serves as a rough approximation of how much the longest axis
//of an ellipse will be scaled by a given matrix
//(in other words, the amount by which its on-screen size changes)
float sxi = projmodelview.m00 * width / 2;
float syi = projmodelview.m10 * height / 2;
float sxj = projmodelview.m01 * width / 2;
float syj = projmodelview.m11 * height / 2;
float Imag2 = sxi * sxi + syi * syi;
float Jmag2 = sxj * sxj + syj * syj;
ellipseDetailMultiplier = PApplet.sqrt(PApplet.max(Imag2, Jmag2));
}
void singleLine(float x1, float y1, float x2, float y2, int color) {
float r = strokeWeight * 0.5f;
float dx = x2 - x1;
float dy = y2 - y1;
float d = PApplet.sqrt(dx*dx + dy*dy);
float tx = dy / d * r;
float ty = dx / d * r;
if (strokeCap == PROJECT) {
x1 -= ty;
x2 += ty;
y1 -= tx;
y2 += tx;
}
triangle(x1 - tx, y1 + ty, x1 + tx, y1 - ty, x2 - tx, y2 + ty, color);
triangle(x2 + tx, y2 - ty, x2 - tx, y2 + ty, x1 + tx, y1 - ty, color);
if (r >= LINE_DETAIL_LIMIT && strokeCap == ROUND) {
int segments = circleDetail(r, HALF_PI);
float step = HALF_PI / segments;
float c = PApplet.cos(step);
float s = PApplet.sin(step);
for (int i = 0; i < segments; ++i) {
//this is the equivalent of multiplying the vector <tx, ty> by the 2x2 rotation matrix [[c -s] [s c]]
float nx = c * tx - s * ty;
float ny = s * tx + c * ty;
triangle(x2, y2, x2 + ty, y2 + tx, x2 + ny, y2 + nx, color);
triangle(x2, y2, x2 - tx, y2 + ty, x2 - nx, y2 + ny, color);
triangle(x1, y1, x1 - ty, y1 - tx, x1 - ny, y1 - nx, color);
triangle(x1, y1, x1 + tx, y1 - ty, x1 + nx, y1 - ny, color);
tx = nx;
ty = ny;
}
}
}
void singlePoint(float x, float y, int color) {
float r = strokeWeight * 0.5f;
if (r >= LINE_DETAIL_LIMIT && strokeCap == ROUND) {
int segments = circleDetail(r);
float step = QUARTER_PI / segments;
float x1 = 0, y1 = r;
float c = PApplet.cos(step);
float s = PApplet.sin(step);
for (int i = 0; i < segments; ++i) {
//this is the equivalent of multiplying the vector <x1, y1> by the 2x2 rotation matrix [[c -s] [s c]]
float x2 = c * x1 - s * y1;
float y2 = s * x1 + c * y1;
triangle(x, y, x + x1, y + y1, x + x2, y + y2, strokeColor);
triangle(x, y, x + x1, y - y1, x + x2, y - y2, strokeColor);
triangle(x, y, x - x1, y + y1, x - x2, y + y2, strokeColor);
triangle(x, y, x - x1, y - y1, x - x2, y - y2, strokeColor);
triangle(x, y, x + y1, y + x1, x + y2, y + x2, strokeColor);
triangle(x, y, x + y1, y - x1, x + y2, y - x2, strokeColor);
triangle(x, y, x - y1, y + x1, x - y2, y + x2, strokeColor);
triangle(x, y, x - y1, y - x1, x - y2, y - x2, strokeColor);
x1 = x2;
y1 = y2;
}
} else {
triangle(x - r, y - r, x + r, y - r, x - r, y + r, color);
triangle(x + r, y - r, x - r, y + r, x + r, y + r, color);
}
}
private class StrokeRenderer {
int lineVertexCount;
float fx, fy;
float sx, sy, sdx, sdy;
float px, py, pdx, pdy;
float lx, ly;
float r;
void arcJoin(float x, float y, float dx1, float dy1, float dx2, float dy2) {
//we don't need to normalize before doing these products
//since the vectors are the same length and only used as arguments to atan2()
float cross = dx1 * dy2 - dy1 * dx2;
float dot = dx1 * dx2 + dy1 * dy2;
float theta = PApplet.atan2(cross, dot);
int segments = circleDetail(r, theta);
float px = x + dx1, py = y + dy1;
if (segments > 1) {
float c = PApplet.cos(theta / segments);
float s = PApplet.sin(theta / segments);
for (int i = 1; i < segments; ++i) {
//this is the equivalent of multiplying the vector <dx1, dy1> by the 2x2 rotation matrix [[c -s] [s c]]
float tempx = c * dx1 - s * dy1;
dy1 = s * dx1 + c * dy1;
dx1 = tempx;
float nx = x + dx1;
float ny = y + dy1;
triangle(x, y, px, py, nx, ny, strokeColor);
px = nx;
py = ny;
}
}
triangle(x, y, px, py, x + dx2, y + dy2, strokeColor);
}
void beginLine() {
lineVertexCount = 0;
r = strokeWeight * 0.5f;
}
void lineVertex(float x, float y) {
//disallow adding consecutive duplicate vertices,
//as it is pointless and just creates an extra edge case
if (lineVertexCount > 0 && x == lx && y == ly) {
return;
}
if (lineVertexCount == 0) {
fx = x;
fy = y;
} else if (r < LINE_DETAIL_LIMIT) {
singleLine(lx, ly, x, y, strokeColor);
} else if (lineVertexCount == 1) {
sx = x;
sy = y;
} else {
//calculate normalized direction vectors for each leg
float leg1x = lx - px;
float leg1y = ly - py;
float leg2x = x - lx;
float leg2y = y - ly;
float len1 = PApplet.sqrt(leg1x * leg1x + leg1y * leg1y);
float len2 = PApplet.sqrt(leg2x * leg2x + leg2y * leg2y);
leg1x /= len1;
leg1y /= len1;
leg2x /= len2;
leg2y /= len2;
float legDot = -leg1x * leg2x - leg1y * leg2y;
float cosPiOver15 = 0.97815f;
if (strokeJoin == BEVEL || strokeJoin == ROUND || legDot > cosPiOver15 || legDot < -0.999) {
float tx = leg1y * r;
float ty = -leg1x * r;
if (lineVertexCount == 2) {
sdx = tx;
sdy = ty;
} else {
triangle(px - pdx, py - pdy, px + pdx, py + pdy, lx - tx, ly - ty, strokeColor);
triangle(px + pdx, py + pdy, lx - tx, ly - ty, lx + tx, ly + ty, strokeColor);
}
float nx = leg2y * r;
float ny = -leg2x * r;
float legCross = leg1x * leg2y - leg1y * leg2x;
if (strokeJoin == ROUND) {
if (legCross > 0) {
arcJoin(lx, ly, tx, ty, nx, ny);
} else {
arcJoin(lx, ly, -tx, -ty, -nx, -ny);
}
} else if (legCross > 0) {
triangle(lx, ly, lx + tx, ly + ty, lx + nx, ly + ny, strokeColor);
} else {
triangle(lx, ly, lx - tx, ly - ty, lx - nx, ly - ny, strokeColor);
}
pdx = nx;
pdy = ny;
} else { //miter joint
//find the bisecting vector
float x1 = leg2x - leg1x;
float y1 = leg2y - leg1y;
//find a (normalized) vector perpendicular to one of the legs
float x2 = leg1y;
float y2 = -leg1x;
//scale the bisecting vector to the correct length using magic (not sure how to explain this one)
float dot = x1 * x2 + y1 * y2;
float bx = x1 * (r / dot);
float by = y1 * (r / dot);
if (lineVertexCount == 2) {
sdx = bx;
sdy = by;
} else {
triangle(px - pdx, py - pdy, px + pdx, py + pdy, lx - bx, ly - by, strokeColor);
triangle(px + pdx, py + pdy, lx - bx, ly - by, lx + bx, ly + by, strokeColor);
}
pdx = bx;
pdy = by;
}
}
px = lx;
py = ly;
lx = x;
ly = y;
lineVertexCount += 1;
}
void lineCap(float x, float y, float dx, float dy) {
int segments = circleDetail(r, HALF_PI);
float px = dy, py = -dx;
if (segments > 1) {
float c = PApplet.cos(HALF_PI / segments);
float s = PApplet.sin(HALF_PI / segments);
for (int i = 1; i < segments; ++i) {
//this is the equivalent of multiplying the vector <px, py> by the 2x2 rotation matrix [[c -s] [s c]]
float nx = c * px - s * py;
float ny = s * px + c * py;
triangle(x, y, x + px, y + py, x + nx, y + ny, strokeColor);
triangle(x, y, x - py, y + px, x - ny, y + nx, strokeColor);
px = nx;
py = ny;
}
}
triangle(x, y, x + px, y + py, x + dx, y + dy, strokeColor);
triangle(x, y, x - py, y + px, x - dy, y + dx, strokeColor);
}
void endLine(boolean closed) {
if (lineVertexCount < 2) {
return;
}
if (lineVertexCount == 2) {
singleLine(px, py, lx, ly, strokeColor);
return;
}
if (r < LINE_DETAIL_LIMIT) {
if (closed) {
singleLine(lx, ly, fx, fy, strokeColor);
}
return;
}
if (closed) {
//draw the last two legs
lineVertex(fx, fy);
lineVertex(sx, sy);
//connect first and second vertices
triangle(px - pdx, py - pdy, px + pdx, py + pdy, sx - sdx, sy - sdy, strokeColor);
triangle(px + pdx, py + pdy, sx - sdx, sy - sdy, sx + sdx, sy + sdy, strokeColor);
} else {
//draw last line (with cap)
float dx = lx - px;
float dy = ly - py;
float d = PApplet.sqrt(dx*dx + dy*dy);
float tx = dy / d * r;
float ty = -dx / d * r;
if (strokeCap == PROJECT) {
lx -= ty;
ly += tx;
}
triangle(px - pdx, py - pdy, px + pdx, py + pdy, lx - tx, ly - ty, strokeColor);
triangle(px + pdx, py + pdy, lx - tx, ly - ty, lx + tx, ly + ty, strokeColor);
if (strokeCap == ROUND) {
lineCap(lx, ly, -ty, tx);
}
//draw first line (with cap)
dx = fx - sx;
dy = fy - sy;
d = PApplet.sqrt(dx*dx + dy*dy);
tx = dy / d * r;
ty = -dx / d * r;
if (strokeCap == PROJECT) {
fx -= ty;
fy += tx;
}
triangle(sx - sdx, sy - sdy, sx + sdx, sy + sdy, fx + tx, fy + ty, strokeColor);
triangle(sx + sdx, sy + sdy, fx + tx, fy + ty, fx - tx, fy - ty, strokeColor);
if (strokeCap == ROUND) {
lineCap(fx, fy, -ty, tx);
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment