mirror of
https://github.com/arduino/Arduino.git
synced 2024-12-10 21:24:12 +01:00
1673 lines
48 KiB
Java
1673 lines
48 KiB
Java
|
/* -*- mode: jde; c-basic-offset: 2; indent-tabs-mode: nil -*- */
|
||
|
|
||
|
/*
|
||
|
Part of the Processing project - http://processing.org
|
||
|
|
||
|
Copyright (c) 2006 Ben Fry and Casey Reas
|
||
|
|
||
|
This library is free software; you can redistribute it and/or
|
||
|
modify it under the terms of the GNU Lesser General Public
|
||
|
License as published by the Free Software Foundation; either
|
||
|
version 2.1 of the License, or (at your option) any later version.
|
||
|
|
||
|
This library is distributed in the hope that it will be useful,
|
||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||
|
Lesser General Public License for more details.
|
||
|
|
||
|
You should have received a copy of the GNU Lesser General
|
||
|
Public License along with this library; if not, write to the
|
||
|
Free Software Foundation, Inc., 59 Temple Place, Suite 330,
|
||
|
Boston, MA 02111-1307 USA
|
||
|
*/
|
||
|
|
||
|
package processing.core;
|
||
|
|
||
|
import java.awt.Toolkit;
|
||
|
import java.awt.image.DirectColorModel;
|
||
|
import java.awt.image.MemoryImageSource;
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Subclass of PGraphics that handles fast 2D rendering,
|
||
|
* more commonly referred to as P2D. This class uses no Java2D
|
||
|
* and will run with Java 1.1.
|
||
|
*/
|
||
|
public class PGraphics2D extends PGraphics {
|
||
|
|
||
|
PPolygon polygon; // general polygon to use for shape
|
||
|
PPolygon fpolygon; // used to fill polys for tri or quad strips
|
||
|
PPolygon spolygon; // stroke/line polygon
|
||
|
float svertices[][]; // temp vertices used for stroking end of poly
|
||
|
|
||
|
// polygon that handles tesselation
|
||
|
private PPolygon tpolygon;
|
||
|
private int TPOLYGON_MAX_VERTICES = 512;
|
||
|
private int tpolygon_vertex_order[]; // = new int[MAX_VERTICES];
|
||
|
|
||
|
PLine line;
|
||
|
|
||
|
//boolean untransformed;
|
||
|
boolean strokeChanged = true;
|
||
|
boolean fillChanged = true;
|
||
|
|
||
|
static final int CVERTEX_ALLOC = 128;
|
||
|
float cvertex[][] = new float[CVERTEX_ALLOC][VERTEX_FIELD_COUNT];
|
||
|
int cvertexIndex;
|
||
|
|
||
|
|
||
|
//////////////////////////////////////////////////////////////
|
||
|
|
||
|
|
||
|
//protected PGraphics2D() { }
|
||
|
|
||
|
|
||
|
/*
|
||
|
public PGraphics2D(int iwidth, int iheight) {
|
||
|
this(iwidth, iheight, null);
|
||
|
}
|
||
|
*/
|
||
|
|
||
|
|
||
|
public PGraphics2D(int iwidth, int iheight, PApplet applet) {
|
||
|
super(iwidth, iheight, applet);
|
||
|
/*
|
||
|
if (applet != null) {
|
||
|
this.parent = applet;
|
||
|
applet.addListeners();
|
||
|
}
|
||
|
resize(iwidth, iheight);
|
||
|
*/
|
||
|
}
|
||
|
|
||
|
|
||
|
//resize handled by superclass
|
||
|
|
||
|
|
||
|
//requestDisplay handled by superclass
|
||
|
|
||
|
|
||
|
protected void allocate() {
|
||
|
pixelCount = width * height;
|
||
|
pixels = new int[pixelCount];
|
||
|
|
||
|
// because of a java 1.1 bug, pixels must be registered as
|
||
|
// opaque before their first run, the memimgsrc will flicker
|
||
|
// and run very slowly.
|
||
|
backgroundColor |= 0xff000000; // just for good measure
|
||
|
for (int i = 0; i < pixelCount; i++) pixels[i] = backgroundColor;
|
||
|
//for (int i = 0; i < pixelCount; i++) pixels[i] = 0xffffffff;
|
||
|
|
||
|
//if (parent != null) {
|
||
|
if (mainDrawingSurface) {
|
||
|
cm = new DirectColorModel(32, 0x00ff0000, 0x0000ff00, 0x000000ff);;
|
||
|
mis = new MemoryImageSource(width, height, pixels, 0, width);
|
||
|
mis.setFullBufferUpdates(true);
|
||
|
mis.setAnimated(true);
|
||
|
image = Toolkit.getDefaultToolkit().createImage(mis);
|
||
|
}
|
||
|
|
||
|
// can't un-set this because this may be only a resize (Bug #463)
|
||
|
//defaultsInited = false;
|
||
|
}
|
||
|
|
||
|
|
||
|
//////////////////////////////////////////////////////////////
|
||
|
|
||
|
|
||
|
public void beginDraw() {
|
||
|
insideResizeWait();
|
||
|
insideDraw = true;
|
||
|
|
||
|
// need to call defaults(), but can only be done when it's ok
|
||
|
// to draw (i.e. for opengl, no drawing can be done outside
|
||
|
// beginDraw/endDraw).
|
||
|
if (!defaultsInited) {
|
||
|
defaults();
|
||
|
|
||
|
polygon = new PPolygon(this);
|
||
|
fpolygon = new PPolygon(this);
|
||
|
spolygon = new PPolygon(this);
|
||
|
spolygon.vertexCount = 4;
|
||
|
svertices = new float[2][];
|
||
|
}
|
||
|
|
||
|
resetMatrix(); // reset model matrix
|
||
|
|
||
|
// reset vertices
|
||
|
vertexCount = 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
public void endDraw() {
|
||
|
// moving this back here (post-68) because of macosx thread problem
|
||
|
if (mis != null) {
|
||
|
mis.newPixels(pixels, cm, 0, width);
|
||
|
}
|
||
|
// mark pixels as having been updated, so that they'll work properly
|
||
|
// when this PGraphics is drawn using image().
|
||
|
updatePixels();
|
||
|
|
||
|
insideDraw = false;
|
||
|
}
|
||
|
|
||
|
|
||
|
//////////////////////////////////////////////////////////////
|
||
|
|
||
|
|
||
|
public void beginShape(int kind) {
|
||
|
shape = kind;
|
||
|
vertexCount = 0;
|
||
|
splineVertexCount = 0;
|
||
|
|
||
|
polygon.reset(0);
|
||
|
fpolygon.reset(4);
|
||
|
spolygon.reset(4);
|
||
|
|
||
|
polygon.interpUV = false;
|
||
|
}
|
||
|
|
||
|
|
||
|
// PGraphics will throw a depthError
|
||
|
//public void normal(float nx, float ny, float nz)
|
||
|
|
||
|
// PGraphics will handle setting these
|
||
|
//public void textureMode(int mode)
|
||
|
//public void texture(PImage image)
|
||
|
//protected void textureVertex(float u, float v)
|
||
|
|
||
|
|
||
|
public void vertex(float x, float y) {
|
||
|
float vertex[] = polygon.nextVertex();
|
||
|
cvertexIndex = 0; // reset curves to start
|
||
|
|
||
|
vertex[MX] = x;
|
||
|
vertex[MY] = y;
|
||
|
|
||
|
if (fill) {
|
||
|
vertex[R] = fillR;
|
||
|
vertex[G] = fillG;
|
||
|
vertex[B] = fillB;
|
||
|
vertex[A] = fillA;
|
||
|
}
|
||
|
|
||
|
if (stroke) {
|
||
|
vertex[SR] = strokeR;
|
||
|
vertex[SG] = strokeG;
|
||
|
vertex[SB] = strokeB;
|
||
|
vertex[SA] = strokeA;
|
||
|
vertex[SW] = strokeWeight;
|
||
|
}
|
||
|
|
||
|
// this complicated if construct may defeat the purpose
|
||
|
if (textureImage != null) {
|
||
|
vertex[U] = textureU;
|
||
|
vertex[V] = textureV;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
public void vertex(float x, float y, float u, float v) {
|
||
|
textureVertex(u, v);
|
||
|
vertex(x, y);
|
||
|
}
|
||
|
|
||
|
|
||
|
public void vertex(float x, float y, float z) {
|
||
|
depthErrorXYZ("vertex");
|
||
|
}
|
||
|
|
||
|
|
||
|
public void vertex(float x, float y, float z, float u, float v) {
|
||
|
depthErrorXYZ("vertex");
|
||
|
}
|
||
|
|
||
|
|
||
|
public void endShape(int mode) {
|
||
|
// clear the 'shape drawing' flag in case of early exit
|
||
|
//shape = 0;
|
||
|
// hm can't do anymore..
|
||
|
|
||
|
int polyVertexCount = polygon.vertexCount;
|
||
|
float polyVertices[][] = polygon.vertices;
|
||
|
|
||
|
if (untransformed()) {
|
||
|
for (int i = 0; i < polyVertexCount; i++) {
|
||
|
polyVertices[i][X] = polyVertices[i][MX];
|
||
|
polyVertices[i][Y] = polyVertices[i][MY];
|
||
|
}
|
||
|
} else {
|
||
|
for (int i = 0; i < polyVertexCount; i++) {
|
||
|
polyVertices[i][X] = m00*polyVertices[i][MX] + m01*polyVertices[i][MY] + m03;
|
||
|
polyVertices[i][Y] = m10*polyVertices[i][MX] + m11*polyVertices[i][MY] + m13;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// ------------------------------------------------------------------
|
||
|
// TEXTURES
|
||
|
|
||
|
if (polygon.interpUV) {
|
||
|
fpolygon.texture(textureImage); //polygon.timage);
|
||
|
}
|
||
|
|
||
|
|
||
|
// ------------------------------------------------------------------
|
||
|
// COLORS
|
||
|
// calculate RGB for each vertex
|
||
|
|
||
|
spolygon.interpARGB = strokeChanged; //false;
|
||
|
fpolygon.interpARGB = fillChanged; //false;
|
||
|
|
||
|
// all the values for r, g, b have been set with calls to vertex()
|
||
|
// (no need to re-calculate anything here)
|
||
|
|
||
|
|
||
|
// ------------------------------------------------------------------
|
||
|
// RENDER SHAPES
|
||
|
|
||
|
int increment;
|
||
|
|
||
|
switch (shape) {
|
||
|
case POINTS:
|
||
|
if (untransformed() && (strokeWeight == 1)) {
|
||
|
if (!strokeChanged) {
|
||
|
for (int i = 0; i < polyVertexCount; i++) {
|
||
|
thin_point((int) polyVertices[i][X], (int) polyVertices[i][Y],
|
||
|
0, strokeColor);
|
||
|
}
|
||
|
} else {
|
||
|
for (int i = 0; i < polyVertexCount; i++) {
|
||
|
thin_point((int) polyVertices[i][X], (int) polyVertices[i][Y],
|
||
|
0, float_color(polyVertices[i][SR],
|
||
|
polyVertices[i][SG],
|
||
|
polyVertices[i][SB]));
|
||
|
}
|
||
|
//strokei = strokeiSaved;
|
||
|
}
|
||
|
} else {
|
||
|
float f[] = polyVertices[0];
|
||
|
|
||
|
for (int i = 0; i < polyVertexCount; i++) {
|
||
|
float v[] = polyVertices[i];
|
||
|
|
||
|
// if this is the first time (i == 0)
|
||
|
// or if lighting is enabled
|
||
|
// or the stroke color has changed inside beginShape/endShape
|
||
|
// then re-calculate the color at this vertex
|
||
|
if ((i == 0) || strokeChanged) {
|
||
|
// push calculated color into 'f' (this way, f is always valid)
|
||
|
calc_lighting(v[SR], v[SG], v[SB],
|
||
|
v[X], v[Y], v[Z],
|
||
|
v[NX], v[NY], v[NZ], f, R);
|
||
|
}
|
||
|
// uses [SA], since stroke alpha isn't moved into [A] the
|
||
|
// way that [SR] goes to [R] etc on the calc_lighting call
|
||
|
// (there's no sense in copying it to [A], except consistency
|
||
|
// in the code.. but why the extra slowness?)
|
||
|
thick_point(v[X], v[Y], v[Z], f[R], f[G], f[B], f[SA]);
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case LINES:
|
||
|
//case LINE_STRIP:
|
||
|
//case LINE_LOOP:
|
||
|
if (!stroke) return;
|
||
|
|
||
|
// if it's a line loop, copy the vertex data to the last element
|
||
|
//if (shape == LINE_LOOP) {
|
||
|
if (mode == CLOSE) {
|
||
|
float v0[] = polygon.vertices[0];
|
||
|
float v1[] = polygon.nextVertex();
|
||
|
polyVertexCount++; // since it had already been read above
|
||
|
|
||
|
v1[X] = v0[X]; v1[Y] = v0[Y]; v1[Z] = v0[Z];
|
||
|
v1[SR] = v0[SR]; v1[SG] = v0[SG]; v1[SB] = v0[SB];
|
||
|
}
|
||
|
|
||
|
// increment by two for individual lines
|
||
|
increment = (shape == LINES) ? 2 : 1;
|
||
|
draw_lines(polyVertices, polyVertexCount-1, 1, increment, 0);
|
||
|
break;
|
||
|
|
||
|
case TRIANGLES:
|
||
|
case TRIANGLE_STRIP:
|
||
|
increment = (shape == TRIANGLES) ? 3 : 1;
|
||
|
// do fill and stroke separately because otherwise
|
||
|
// the lines will be stroked more than necessary
|
||
|
if (fill) {
|
||
|
fpolygon.vertexCount = 3;
|
||
|
for (int i = 0; i < polyVertexCount-2; i += increment) {
|
||
|
for (int j = 0; j < 3; j++) {
|
||
|
fpolygon.vertices[j][R] = polyVertices[i+j][R];
|
||
|
fpolygon.vertices[j][G] = polyVertices[i+j][G];
|
||
|
fpolygon.vertices[j][B] = polyVertices[i+j][B];
|
||
|
fpolygon.vertices[j][A] = polyVertices[i+j][A];
|
||
|
|
||
|
fpolygon.vertices[j][X] = polyVertices[i+j][X];
|
||
|
fpolygon.vertices[j][Y] = polyVertices[i+j][Y];
|
||
|
fpolygon.vertices[j][Z] = polyVertices[i+j][Z];
|
||
|
|
||
|
if (polygon.interpUV) {
|
||
|
fpolygon.vertices[j][U] = polyVertices[i+j][U];
|
||
|
fpolygon.vertices[j][V] = polyVertices[i+j][V];
|
||
|
}
|
||
|
}
|
||
|
fpolygon.render();
|
||
|
}
|
||
|
}
|
||
|
if (stroke) {
|
||
|
// first draw all vertices as a line strip
|
||
|
if (shape == TRIANGLE_STRIP) {
|
||
|
draw_lines(polyVertices, polyVertexCount-1, 1, 1, 0);
|
||
|
} else {
|
||
|
draw_lines(polyVertices, polyVertexCount-1, 1, 1, 3);
|
||
|
}
|
||
|
// then draw from vertex (n) to (n+2)
|
||
|
// incrementing n using the same as above
|
||
|
draw_lines(polyVertices, polyVertexCount-2, 2, increment, 0);
|
||
|
// changed this to vertexCount-2, because it seemed
|
||
|
// to be adding an extra (nonexistant) line
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case QUADS:
|
||
|
case QUAD_STRIP:
|
||
|
//System.out.println("pooping out a quad");
|
||
|
increment = (shape == QUADS) ? 4 : 2;
|
||
|
if (fill) {
|
||
|
fpolygon.vertexCount = 4;
|
||
|
for (int i = 0; i < polyVertexCount-3; i += increment) {
|
||
|
for (int j = 0; j < 4; j++) {
|
||
|
fpolygon.vertices[j][R] = polyVertices[i+j][R];
|
||
|
fpolygon.vertices[j][G] = polyVertices[i+j][G];
|
||
|
fpolygon.vertices[j][B] = polyVertices[i+j][B];
|
||
|
fpolygon.vertices[j][A] = polyVertices[i+j][A];
|
||
|
|
||
|
fpolygon.vertices[j][X] = polyVertices[i+j][X];
|
||
|
fpolygon.vertices[j][Y] = polyVertices[i+j][Y];
|
||
|
fpolygon.vertices[j][Z] = polyVertices[i+j][Z];
|
||
|
|
||
|
if (polygon.interpUV) {
|
||
|
fpolygon.vertices[j][U] = polyVertices[i+j][U];
|
||
|
fpolygon.vertices[j][V] = polyVertices[i+j][V];
|
||
|
}
|
||
|
}
|
||
|
fpolygon.render();
|
||
|
}
|
||
|
}
|
||
|
if (stroke) {
|
||
|
// first draw all vertices as a line strip
|
||
|
if (shape == QUAD_STRIP) {
|
||
|
draw_lines(polyVertices, polyVertexCount-1, 1, 1, 0);
|
||
|
} else { // skip every few for quads
|
||
|
draw_lines(polyVertices, polyVertexCount, 1, 1, 4);
|
||
|
}
|
||
|
// then draw from vertex (n) to (n+3)
|
||
|
// incrementing n by the same increment as above
|
||
|
draw_lines(polyVertices, polyVertexCount-2, 3, increment, 0);
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case POLYGON:
|
||
|
if (isConvex()) {
|
||
|
if (fill) {
|
||
|
polygon.render();
|
||
|
if (stroke) polygon.unexpand();
|
||
|
}
|
||
|
|
||
|
if (stroke) {
|
||
|
draw_lines(polyVertices, polyVertexCount-1, 1, 1, 0);
|
||
|
// draw the last line connecting back to the first point in poly
|
||
|
svertices[0] = polyVertices[polyVertexCount-1];
|
||
|
svertices[1] = polyVertices[0];
|
||
|
draw_lines(svertices, 1, 1, 1, 0);
|
||
|
}
|
||
|
} else {
|
||
|
if (fill) {
|
||
|
// the triangulator produces polygons that don't align
|
||
|
// when smoothing is enabled. but if there is a stroke around
|
||
|
// the polygon, then smoothing can be temporarily disabled.
|
||
|
boolean smoov = smooth;
|
||
|
//if (stroke && !hints[DISABLE_SMOOTH_HACK]) smooth = false;
|
||
|
if (stroke) smooth = false;
|
||
|
concaveRender();
|
||
|
//if (stroke && !hints[DISABLE_SMOOTH_HACK]) smooth = smoov;
|
||
|
if (stroke) smooth = smoov;
|
||
|
}
|
||
|
|
||
|
if (stroke) {
|
||
|
draw_lines(polyVertices, polyVertexCount-1, 1, 1, 0);
|
||
|
// draw the last line connecting back
|
||
|
// to the first point in poly
|
||
|
svertices[0] = polyVertices[polyVertexCount-1];
|
||
|
svertices[1] = polyVertices[0];
|
||
|
draw_lines(svertices, 1, 1, 1, 0);
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// to signify no shape being drawn
|
||
|
shape = 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
//////////////////////////////////////////////////////////////
|
||
|
|
||
|
// CONCAVE/CONVEX POLYGONS
|
||
|
|
||
|
|
||
|
private boolean isConvex() {
|
||
|
float v[][] = polygon.vertices;
|
||
|
int n = polygon.vertexCount;
|
||
|
int j,k;
|
||
|
int flag = 0;
|
||
|
float z;
|
||
|
//float tol = 0.001f;
|
||
|
|
||
|
if (n < 3)
|
||
|
// ERROR: this is a line or a point, render with CONVEX
|
||
|
return true;
|
||
|
|
||
|
// iterate along border doing dot product.
|
||
|
// if the sign of the result changes, then is concave
|
||
|
for (int i=0;i<n;i++) {
|
||
|
j = (i + 1) % n;
|
||
|
k = (i + 2) % n;
|
||
|
z = (v[j][X] - v[i][X]) * (v[k][Y] - v[j][Y]);
|
||
|
z -= (v[j][Y] - v[i][Y]) * (v[k][X] - v[j][X]);
|
||
|
if (z < 0)
|
||
|
flag |= 1;
|
||
|
else if (z > 0)
|
||
|
flag |= 2;
|
||
|
if (flag == 3)
|
||
|
return false; // CONCAVE
|
||
|
}
|
||
|
if (flag != 0)
|
||
|
return true; // CONVEX
|
||
|
else
|
||
|
// ERROR: colinear points, self intersection
|
||
|
// treat as CONVEX
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
|
||
|
// triangulate the current polygon
|
||
|
private void concaveRender() {
|
||
|
// WARNING: code is not in optimum form
|
||
|
// local initiations of some variables are made to
|
||
|
// keep the code modular and easy to integrate
|
||
|
// restet triangle
|
||
|
float polyVertices[][] = polygon.vertices;
|
||
|
|
||
|
if (tpolygon == null) {
|
||
|
// allocate on first use, rather than slowing
|
||
|
// the startup of the class.
|
||
|
tpolygon = new PPolygon(this);
|
||
|
tpolygon_vertex_order = new int[TPOLYGON_MAX_VERTICES];
|
||
|
}
|
||
|
tpolygon.reset(3);
|
||
|
|
||
|
// copy render parameters
|
||
|
|
||
|
if (textureImage != null) {
|
||
|
tpolygon.texture(textureImage); //polygon.timage);
|
||
|
}
|
||
|
|
||
|
tpolygon.interpX = polygon.interpX;
|
||
|
tpolygon.interpZ = polygon.interpZ;
|
||
|
tpolygon.interpUV = polygon.interpUV;
|
||
|
tpolygon.interpARGB = polygon.interpARGB;
|
||
|
|
||
|
// simple ear clipping polygon triangulation
|
||
|
// addapted from code by john w. ratcliff (jratcliff@verant.com)
|
||
|
|
||
|
// 1 - first we check if the polygon goes CW or CCW
|
||
|
// CW-CCW ordering adapted from code by
|
||
|
// Joseph O'Rourke orourke@cs.smith.edu
|
||
|
// 1A - we start by finding the lowest-right most vertex
|
||
|
|
||
|
boolean ccw = false; // clockwise
|
||
|
|
||
|
int n = polygon.vertexCount;
|
||
|
int mm; // postion for LR vertex
|
||
|
float min[] = new float[2];
|
||
|
|
||
|
min[X] = polyVertices[0][X];
|
||
|
min[Y] = polyVertices[0][Y];
|
||
|
mm = 0;
|
||
|
|
||
|
for(int i = 0; i < n; i++ ) {
|
||
|
if( (polyVertices[i][Y] < min[Y]) ||
|
||
|
( (polyVertices[i][Y] == min[Y]) && (polyVertices[i][X] > min[X]) )
|
||
|
) {
|
||
|
mm = i;
|
||
|
min[X] = polyVertices[mm][X];
|
||
|
min[Y] = polyVertices[mm][Y];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// 1B - now we compute the cross product of the edges of this vertex
|
||
|
float cp;
|
||
|
int mm1;
|
||
|
|
||
|
// just for renaming
|
||
|
float a[] = new float[2];
|
||
|
float b[] = new float[2];
|
||
|
float c[] = new float[2];
|
||
|
|
||
|
mm1 = (mm + (n-1)) % n;
|
||
|
|
||
|
// assign a[0] to point to poly[m1][0] etc.
|
||
|
for(int i = 0; i < 2; i++ ) {
|
||
|
a[i] = polyVertices[mm1][i];
|
||
|
b[i] = polyVertices[mm][i];
|
||
|
c[i] = polyVertices[(mm+1)%n][i];
|
||
|
}
|
||
|
|
||
|
cp = a[0] * b[1] - a[1] * b[0] +
|
||
|
a[1] * c[0] - a[0] * c[1] +
|
||
|
b[0] * c[1] - c[0] * b[1];
|
||
|
|
||
|
if ( cp > 0 )
|
||
|
ccw = true; // CCW
|
||
|
else
|
||
|
ccw = false; // CW
|
||
|
|
||
|
// 1C - then we sort the vertices so they
|
||
|
// are always in a counterclockwise order
|
||
|
//int j = 0;
|
||
|
if (!ccw) {
|
||
|
// keep the same order
|
||
|
for (int i = 0; i < n; i++) {
|
||
|
tpolygon_vertex_order[i] = i;
|
||
|
}
|
||
|
|
||
|
} else {
|
||
|
// invert the order
|
||
|
for (int i = 0; i < n; i++) {
|
||
|
tpolygon_vertex_order[i] = (n - 1) - i;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// 2 - begin triangulation
|
||
|
// resulting triangles are stored in the triangle array
|
||
|
// remove vc-2 Vertices, creating 1 triangle every time
|
||
|
int vc = n;
|
||
|
int count = 2*vc; // complex polygon detection
|
||
|
|
||
|
for (int m = 0, v = vc - 1; vc > 2; ) {
|
||
|
boolean snip = true;
|
||
|
|
||
|
// if we start over again, is a complex polygon
|
||
|
if (0 >= (count--)) {
|
||
|
break; // triangulation failed
|
||
|
}
|
||
|
|
||
|
// get 3 consecutive vertices <u,v,w>
|
||
|
int u = v ; if (vc <= u) u = 0; // previous
|
||
|
v = u+1; if (vc <= v) v = 0; // current
|
||
|
int w = v+1; if (vc <= w) w = 0; // next
|
||
|
|
||
|
// triangle A B C
|
||
|
float Ax, Ay, Bx, By, Cx, Cy, Px, Py;
|
||
|
|
||
|
Ax = -polyVertices[tpolygon_vertex_order[u]][X];
|
||
|
Ay = polyVertices[tpolygon_vertex_order[u]][Y];
|
||
|
Bx = -polyVertices[tpolygon_vertex_order[v]][X];
|
||
|
By = polyVertices[tpolygon_vertex_order[v]][Y];
|
||
|
Cx = -polyVertices[tpolygon_vertex_order[w]][X];
|
||
|
Cy = polyVertices[tpolygon_vertex_order[w]][Y];
|
||
|
|
||
|
if ( EPSILON > (((Bx-Ax) * (Cy-Ay)) - ((By-Ay) * (Cx-Ax)))) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
for (int p = 0; p < vc; p++) {
|
||
|
|
||
|
// this part is a bit osbscure, basically what it does
|
||
|
// is test if this tree vertices are and ear or not, looking for
|
||
|
// intersections with the remaining vertices using a cross product
|
||
|
float ax, ay, bx, by, cx, cy, apx, apy, bpx, bpy, cpx, cpy;
|
||
|
float cCROSSap, bCROSScp, aCROSSbp;
|
||
|
|
||
|
if( (p == u) || (p == v) || (p == w) ) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
Px = -polyVertices[tpolygon_vertex_order[p]][X];
|
||
|
Py = polyVertices[tpolygon_vertex_order[p]][Y];
|
||
|
|
||
|
ax = Cx - Bx; ay = Cy - By;
|
||
|
bx = Ax - Cx; by = Ay - Cy;
|
||
|
cx = Bx - Ax; cy = By - Ay;
|
||
|
apx= Px - Ax; apy= Py - Ay;
|
||
|
bpx= Px - Bx; bpy= Py - By;
|
||
|
cpx= Px - Cx; cpy= Py - Cy;
|
||
|
|
||
|
aCROSSbp = ax * bpy - ay * bpx;
|
||
|
cCROSSap = cx * apy - cy * apx;
|
||
|
bCROSScp = bx * cpy - by * cpx;
|
||
|
|
||
|
if ((aCROSSbp >= 0.0f) && (bCROSScp >= 0.0f) && (cCROSSap >= 0.0f)) {
|
||
|
snip = false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (snip) {
|
||
|
// yes, the trio is an ear, render it and cut it
|
||
|
|
||
|
int triangle_vertices[] = new int[3];
|
||
|
int s,t;
|
||
|
|
||
|
// true names of the vertices
|
||
|
triangle_vertices[0] = tpolygon_vertex_order[u];
|
||
|
triangle_vertices[1] = tpolygon_vertex_order[v];
|
||
|
triangle_vertices[2] = tpolygon_vertex_order[w];
|
||
|
|
||
|
// create triangle
|
||
|
//render_triangle(triangle_vertices);
|
||
|
//private final void render_triangle(int[] triangle_vertices) {
|
||
|
// copy all fields of the triangle vertices
|
||
|
for (int i = 0; i < 3; i++) {
|
||
|
float[] src = polygon.vertices[triangle_vertices[i]];
|
||
|
float[] dest = tpolygon.vertices[i];
|
||
|
for (int k = 0; k < VERTEX_FIELD_COUNT; k++) {
|
||
|
dest[k] = src[k];
|
||
|
}
|
||
|
}
|
||
|
// render triangle
|
||
|
tpolygon.render();
|
||
|
//}
|
||
|
|
||
|
m++;
|
||
|
|
||
|
// remove v from remaining polygon
|
||
|
for( s = v, t = v + 1; t < vc; s++, t++) {
|
||
|
tpolygon_vertex_order[s] = tpolygon_vertex_order[t];
|
||
|
}
|
||
|
|
||
|
vc--;
|
||
|
|
||
|
// resest error detection counter
|
||
|
count = 2 * vc;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
//////////////////////////////////////////////////////////////
|
||
|
|
||
|
// RECT
|
||
|
|
||
|
|
||
|
protected void rectImpl(float x1f, float y1f, float x2f, float y2f) {
|
||
|
|
||
|
if (untransformed() && !fillAlpha) {
|
||
|
int x1 = (int) x1f;
|
||
|
int y1 = (int) y1f;
|
||
|
int x2 = (int) x2f;
|
||
|
int y2 = (int) y2f;
|
||
|
|
||
|
rectImplFillUntranSolidRGB(x1, y1, x2, y2);
|
||
|
|
||
|
if (stroke) {
|
||
|
if (strokeWeight == 1) {
|
||
|
thin_flat_line(x1, y1, x2, y1);
|
||
|
thin_flat_line(x2, y1, x2, y2);
|
||
|
thin_flat_line(x2, y2, x1, y2);
|
||
|
thin_flat_line(x1, y2, x1, y1);
|
||
|
|
||
|
} else {
|
||
|
thick_flat_line(x1, y1, fillR, fillG, fillB, fillA,
|
||
|
x2, y1, fillR, fillG, fillB, fillA);
|
||
|
thick_flat_line(x2, y1, fillR, fillG, fillB, fillA,
|
||
|
x2, y2, fillR, fillG, fillB, fillA);
|
||
|
thick_flat_line(x2, y2, fillR, fillG, fillB, fillA,
|
||
|
x1, y2, fillR, fillG, fillB, fillA);
|
||
|
thick_flat_line(x1, y2, fillR, fillG, fillB, fillA,
|
||
|
x1, y1, fillR, fillG, fillB, fillA);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
} else {
|
||
|
beginShape(QUADS);
|
||
|
vertex(x1f, y1f);
|
||
|
vertex(x2f, y1f);
|
||
|
vertex(x2f, y2f);
|
||
|
vertex(x1f, y2f);
|
||
|
endShape();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Draw an untransformed rectangle with no alpha.
|
||
|
*/
|
||
|
private void rectImplFillUntranSolidRGB(int x1, int y1, int x2, int y2) {
|
||
|
//System.out.println("flat quad");
|
||
|
if (y2 < y1) {
|
||
|
int temp = y1; y1 = y2; y2 = temp;
|
||
|
}
|
||
|
if (x2 < x1) {
|
||
|
int temp = x1; x1 = x2; x2 = temp;
|
||
|
}
|
||
|
// checking to watch out for boogers
|
||
|
if ((x1 > width1) || (x2 < 0) ||
|
||
|
(y1 > height1) || (y2 < 0)) return;
|
||
|
|
||
|
//if (fill) {
|
||
|
int fx1 = x1;
|
||
|
int fy1 = y1;
|
||
|
int fx2 = x2;
|
||
|
int fy2 = y2;
|
||
|
|
||
|
// these only affect the fill, not the stroke
|
||
|
// (otherwise strange boogers at edges b/c frame changes shape)
|
||
|
if (fx1 < 0) fx1 = 0;
|
||
|
if (fx2 > width) fx2 = width;
|
||
|
if (fy1 < 0) fy1 = 0;
|
||
|
if (fy2 > height) fy2 = height;
|
||
|
|
||
|
// [toxi 031223]
|
||
|
// on avg. 20-25% faster fill routine using System.arraycopy()
|
||
|
int ww = fx2 - fx1;
|
||
|
int hh = fy2 - fy1;
|
||
|
int[] row = new int[ww];
|
||
|
for (int i = 0; i < ww; i++) row[i] = fillColor;
|
||
|
int idx = fy1 * width + fx1;
|
||
|
for (int y = 0; y < hh; y++) {
|
||
|
System.arraycopy(row, 0, pixels, idx, ww);
|
||
|
idx += width;
|
||
|
}
|
||
|
row = null;
|
||
|
//}
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
//////////////////////////////////////////////////////////////
|
||
|
|
||
|
// ELLIPSE AND ARC
|
||
|
|
||
|
|
||
|
public void ellipseImpl(float x1, float y1, float w, float h) {
|
||
|
if (!smooth && (strokeWeight == 1) &&
|
||
|
!fillAlpha && !strokeAlpha && untransformed()) {
|
||
|
float hradius = w / 2f;
|
||
|
float vradius = h / 2f;
|
||
|
|
||
|
int centerX = (int) (x1 + hradius);
|
||
|
int centerY = (int) (y1 + vradius);
|
||
|
|
||
|
if (hradius == vradius) {
|
||
|
flat_circle(centerX, centerY, (int)hradius);
|
||
|
|
||
|
} else {
|
||
|
flat_ellipse(centerX, centerY, (int)hradius, (int)vradius);
|
||
|
}
|
||
|
} else {
|
||
|
super.ellipseImpl(x1, y1, w, h);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
private void flat_circle(int centerX, int centerY, int radius) {
|
||
|
if (unwarped()) {
|
||
|
float x = m00*centerX + m01*centerY + m02;
|
||
|
float y = m10*centerX + m11*centerY + m12;
|
||
|
centerX = (int)x;
|
||
|
centerY = (int)y;
|
||
|
}
|
||
|
if (fill) flat_circle_fill(centerX, centerY, radius);
|
||
|
if (stroke) flat_circle_stroke(centerX, centerY, radius);
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Draw the outline around a flat circle using a bresenham-style
|
||
|
* algorithm. Adapted from drawCircle function in "Computer Graphics
|
||
|
* for Java Programmers" by Leen Ammeraal, p. 110.
|
||
|
* <P/>
|
||
|
* This function is included because the quality is so much better,
|
||
|
* and the drawing significantly faster than with adaptive ellipses
|
||
|
* drawn using the sine/cosine tables.
|
||
|
* <P/>
|
||
|
* Circle quadrants break down like so:
|
||
|
* <PRE>
|
||
|
* |
|
||
|
* \ NNW | NNE /
|
||
|
* \ | /
|
||
|
* WNW \ | / ENE
|
||
|
* -------------------
|
||
|
* WSW / | \ ESE
|
||
|
* / | \
|
||
|
* / SSW | SSE \
|
||
|
* |
|
||
|
* </PRE>
|
||
|
* @param xc x center
|
||
|
* @param yc y center
|
||
|
* @param r radius
|
||
|
*/
|
||
|
private void flat_circle_stroke(int xC, int yC, int r) {
|
||
|
int x = 0, y = r, u = 1, v = 2 * r - 1, E = 0;
|
||
|
while (x < y) {
|
||
|
thin_point(xC + x, yC + y, 0, strokeColor); // NNE
|
||
|
thin_point(xC + y, yC - x, 0, strokeColor); // ESE
|
||
|
thin_point(xC - x, yC - y, 0, strokeColor); // SSW
|
||
|
thin_point(xC - y, yC + x, 0, strokeColor); // WNW
|
||
|
|
||
|
x++; E += u; u += 2;
|
||
|
if (v < 2 * E) {
|
||
|
y--; E -= v; v -= 2;
|
||
|
}
|
||
|
if (x > y) break;
|
||
|
|
||
|
thin_point(xC + y, yC + x, 0, strokeColor); // ENE
|
||
|
thin_point(xC + x, yC - y, 0, strokeColor); // SSE
|
||
|
thin_point(xC - y, yC - x, 0, strokeColor); // WSW
|
||
|
thin_point(xC - x, yC + y, 0, strokeColor); // NNW
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Heavily adapted version of the above algorithm that handles
|
||
|
* filling the ellipse. Works by drawing from the center and
|
||
|
* outwards to the points themselves. Has to be done this way
|
||
|
* because the values for the points are changed halfway through
|
||
|
* the function, making it impossible to just store a series of
|
||
|
* left and right edges to be drawn more quickly.
|
||
|
*
|
||
|
* @param xc x center
|
||
|
* @param yc y center
|
||
|
* @param r radius
|
||
|
*/
|
||
|
private void flat_circle_fill(int xc, int yc, int r) {
|
||
|
int x = 0, y = r, u = 1, v = 2 * r - 1, E = 0;
|
||
|
while (x < y) {
|
||
|
for (int xx = xc; xx < xc + x; xx++) { // NNE
|
||
|
thin_point(xx, yc + y, 0, fillColor);
|
||
|
}
|
||
|
for (int xx = xc; xx < xc + y; xx++) { // ESE
|
||
|
thin_point(xx, yc - x, 0, fillColor);
|
||
|
}
|
||
|
for (int xx = xc - x; xx < xc; xx++) { // SSW
|
||
|
thin_point(xx, yc - y, 0, fillColor);
|
||
|
}
|
||
|
for (int xx = xc - y; xx < xc; xx++) { // WNW
|
||
|
thin_point(xx, yc + x, 0, fillColor);
|
||
|
}
|
||
|
|
||
|
x++; E += u; u += 2;
|
||
|
if (v < 2 * E) {
|
||
|
y--; E -= v; v -= 2;
|
||
|
}
|
||
|
if (x > y) break;
|
||
|
|
||
|
for (int xx = xc; xx < xc + y; xx++) { // ENE
|
||
|
thin_point(xx, yc + x, 0, fillColor);
|
||
|
}
|
||
|
for (int xx = xc; xx < xc + x; xx++) { // SSE
|
||
|
thin_point(xx, yc - y, 0, fillColor);
|
||
|
}
|
||
|
for (int xx = xc - y; xx < xc; xx++) { // WSW
|
||
|
thin_point(xx, yc - x, 0, fillColor);
|
||
|
}
|
||
|
for (int xx = xc - x; xx < xc; xx++) { // NNW
|
||
|
thin_point(xx, yc + y, 0, fillColor);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// unfortunately this can't handle fill and stroke simultaneously,
|
||
|
// because the fill will later replace some of the stroke points
|
||
|
|
||
|
private final void flat_ellipse_symmetry(int centerX, int centerY,
|
||
|
int ellipseX, int ellipseY,
|
||
|
boolean filling) {
|
||
|
if (filling) {
|
||
|
for (int i = centerX - ellipseX + 1; i < centerX + ellipseX; i++) {
|
||
|
thin_point(i, centerY - ellipseY, 0, fillColor);
|
||
|
thin_point(i, centerY + ellipseY, 0, fillColor);
|
||
|
}
|
||
|
} else {
|
||
|
thin_point(centerX - ellipseX, centerY + ellipseY, 0, strokeColor);
|
||
|
thin_point(centerX + ellipseX, centerY + ellipseY, 0, strokeColor);
|
||
|
thin_point(centerX - ellipseX, centerY - ellipseY, 0, strokeColor);
|
||
|
thin_point(centerX + ellipseX, centerY - ellipseY, 0, strokeColor);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Bresenham-style ellipse drawing function, adapted from a posting to
|
||
|
* comp.graphics.algortihms.
|
||
|
*
|
||
|
* This function is included because the quality is so much better,
|
||
|
* and the drawing significantly faster than with adaptive ellipses
|
||
|
* drawn using the sine/cosine tables.
|
||
|
*
|
||
|
* @param centerX x coordinate of the center
|
||
|
* @param centerY y coordinate of the center
|
||
|
* @param a horizontal radius
|
||
|
* @param b vertical radius
|
||
|
*/
|
||
|
private void flat_ellipse_internal(int centerX, int centerY,
|
||
|
int a, int b, boolean filling) {
|
||
|
int x, y, a2, b2, s, t;
|
||
|
|
||
|
a2 = a*a;
|
||
|
b2 = b*b;
|
||
|
x = 0;
|
||
|
y = b;
|
||
|
s = a2*(1-2*b) + 2*b2;
|
||
|
t = b2 - 2*a2*(2*b-1);
|
||
|
flat_ellipse_symmetry(centerX, centerY, x, y, filling);
|
||
|
|
||
|
do {
|
||
|
if (s < 0) {
|
||
|
s += 2*b2*(2*x+3);
|
||
|
t += 4*b2*(x+1);
|
||
|
x++;
|
||
|
} else if (t < 0) {
|
||
|
s += 2*b2*(2*x+3) - 4*a2*(y-1);
|
||
|
t += 4*b2*(x+1) - 2*a2*(2*y-3);
|
||
|
x++;
|
||
|
y--;
|
||
|
} else {
|
||
|
s -= 4*a2*(y-1);
|
||
|
t -= 2*a2*(2*y-3);
|
||
|
y--;
|
||
|
}
|
||
|
flat_ellipse_symmetry(centerX, centerY, x, y, filling);
|
||
|
|
||
|
} while (y > 0);
|
||
|
}
|
||
|
|
||
|
|
||
|
private void flat_ellipse(int centerX, int centerY, int a, int b) {
|
||
|
if (unwarped()) {
|
||
|
float x = m00*centerX + m01*centerY + m02;
|
||
|
float y = m10*centerX + m11*centerY + m12;
|
||
|
centerX = (int)x;
|
||
|
centerY = (int)y;
|
||
|
}
|
||
|
if (fill) flat_ellipse_internal(centerX, centerY, a, b, true);
|
||
|
if (stroke) flat_ellipse_internal(centerX, centerY, a, b, false);
|
||
|
}
|
||
|
|
||
|
|
||
|
// TODO really need a decent arc function in here..
|
||
|
|
||
|
//protected void arcImpl(float x1, float y1, float w, float h,
|
||
|
// float start, float stop)
|
||
|
|
||
|
|
||
|
|
||
|
//////////////////////////////////////////////////////////////
|
||
|
|
||
|
// BOX & SPHERE
|
||
|
|
||
|
|
||
|
// The PGraphics superclass will throw errors for these fellas
|
||
|
|
||
|
|
||
|
|
||
|
//////////////////////////////////////////////////////////////
|
||
|
|
||
|
// BEZIER & CURVE
|
||
|
|
||
|
|
||
|
public void bezier(float x1, float y1, float z1,
|
||
|
float x2, float y2, float z2,
|
||
|
float x3, float y3, float z3,
|
||
|
float x4, float y4, float z4) {
|
||
|
depthErrorXYZ("bezier");
|
||
|
}
|
||
|
|
||
|
|
||
|
public void curve(float x1, float y1, float z1,
|
||
|
float x2, float y2, float z2,
|
||
|
float x3, float y3, float z3,
|
||
|
float x4, float y4, float z4) {
|
||
|
depthErrorXYZ("curve");
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
//////////////////////////////////////////////////////////////
|
||
|
|
||
|
// IMAGE
|
||
|
|
||
|
|
||
|
protected void imageImpl(PImage image,
|
||
|
float x1, float y1, float x2, float y2,
|
||
|
int u1, int v1, int u2, int v2) {
|
||
|
if ((x2 - x1 == image.width) &&
|
||
|
(y2 - y1 == image.height) &&
|
||
|
!tint && unwarped()) {
|
||
|
flat_image(image, (int) (x1 + m02), (int) (y1 + m12), u1, v1, u2, v2);
|
||
|
|
||
|
} else {
|
||
|
super.imageImpl(image, x1, y1, x2, y2, u1, v1, u2, v2);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Image drawn in flat "screen space", with no scaling or warping.
|
||
|
* this is so common that a special routine is included for it,
|
||
|
* because the alternative is much slower.
|
||
|
*
|
||
|
* @param image image to be drawn
|
||
|
* @param sx1 x coordinate of upper-lefthand corner in screen space
|
||
|
* @param sy1 y coordinate of upper-lefthand corner in screen space
|
||
|
*/
|
||
|
private void flat_image(PImage image, int sx1, int sy1,
|
||
|
int ix1, int iy1, int ix2, int iy2) {
|
||
|
/*
|
||
|
int ix1 = 0;
|
||
|
int iy1 = 0;
|
||
|
int ix2 = image.width;
|
||
|
int iy2 = image.height;
|
||
|
*/
|
||
|
|
||
|
if (imageMode == CENTER) {
|
||
|
sx1 -= image.width / 2;
|
||
|
sy1 -= image.height / 2;
|
||
|
}
|
||
|
|
||
|
int sx2 = sx1 + image.width;
|
||
|
int sy2 = sy1 + image.height;
|
||
|
|
||
|
// don't draw if completely offscreen
|
||
|
// (without this check, ArrayIndexOutOfBoundsException)
|
||
|
if ((sx1 > width1) || (sx2 < 0) ||
|
||
|
(sy1 > height1) || (sy2 < 0)) return;
|
||
|
|
||
|
if (sx1 < 0) { // off left edge
|
||
|
ix1 -= sx1;
|
||
|
sx1 = 0;
|
||
|
}
|
||
|
if (sy1 < 0) { // off top edge
|
||
|
iy1 -= sy1;
|
||
|
sy1 = 0;
|
||
|
}
|
||
|
if (sx2 > width) { // off right edge
|
||
|
ix2 -= sx2 - width;
|
||
|
sx2 = width;
|
||
|
}
|
||
|
if (sy2 > height) { // off bottom edge
|
||
|
iy2 -= sy2 - height;
|
||
|
sy2 = height;
|
||
|
}
|
||
|
|
||
|
int source = iy1 * image.width + ix1;
|
||
|
int target = sy1 * width;
|
||
|
|
||
|
if (image.format == ARGB) {
|
||
|
for (int y = sy1; y < sy2; y++) {
|
||
|
int tx = 0;
|
||
|
|
||
|
for (int x = sx1; x < sx2; x++) {
|
||
|
pixels[target + x] =
|
||
|
_blend(pixels[target + x],
|
||
|
image.pixels[source + tx],
|
||
|
image.pixels[source + tx++] >>> 24);
|
||
|
}
|
||
|
source += image.width;
|
||
|
target += width;
|
||
|
}
|
||
|
} else if (image.format == ALPHA) {
|
||
|
for (int y = sy1; y < sy2; y++) {
|
||
|
int tx = 0;
|
||
|
|
||
|
for (int x = sx1; x < sx2; x++) {
|
||
|
pixels[target + x] =
|
||
|
_blend(pixels[target + x],
|
||
|
fillColor,
|
||
|
image.pixels[source + tx++]);
|
||
|
}
|
||
|
source += image.width;
|
||
|
target += width;
|
||
|
}
|
||
|
|
||
|
} else if (image.format == RGB) {
|
||
|
target += sx1;
|
||
|
int tw = sx2 - sx1;
|
||
|
for (int y = sy1; y < sy2; y++) {
|
||
|
System.arraycopy(image.pixels, source, pixels, target, tw);
|
||
|
// should set z coordinate in here
|
||
|
// or maybe not, since dims=0, meaning no relevant z
|
||
|
source += image.width;
|
||
|
target += width;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//////////////////////////////////////////////////////////////
|
||
|
|
||
|
// TEXT/FONTS
|
||
|
|
||
|
|
||
|
// These will be handled entirely by PGraphics.
|
||
|
|
||
|
|
||
|
|
||
|
//////////////////////////////////////////////////////////////
|
||
|
|
||
|
|
||
|
// expects properly clipped coords, hence does
|
||
|
// NOT check if x/y are in bounds [toxi]
|
||
|
private void thin_pointAt(int x, int y, float z, int color) {
|
||
|
int index = y*width+x; // offset values are pre-calced in constructor
|
||
|
pixels[index] = color;
|
||
|
zbuffer[index] = z;
|
||
|
}
|
||
|
|
||
|
// expects offset/index in pixelbuffer array instead of x/y coords
|
||
|
// used by optimized parts of thin_flat_line() [toxi]
|
||
|
private void thin_pointAtIndex(int offset, float z, int color) {
|
||
|
pixels[offset] = color;
|
||
|
zbuffer[offset] = z;
|
||
|
}
|
||
|
|
||
|
// points are inherently flat, but always tangent
|
||
|
// to the screen surface. the z is only so that things
|
||
|
// get scaled properly if the pt is way in back
|
||
|
private void thick_point(float x, float y, float z, // note floats
|
||
|
float r, float g, float b, float a) {
|
||
|
spolygon.reset(4);
|
||
|
spolygon.interpARGB = false; // no changes for vertices of a point
|
||
|
|
||
|
float strokeWidth2 = strokeWeight/2.0f;
|
||
|
|
||
|
float svertex[] = spolygon.vertices[0];
|
||
|
svertex[X] = x - strokeWidth2;
|
||
|
svertex[Y] = y - strokeWidth2;
|
||
|
svertex[Z] = z;
|
||
|
|
||
|
svertex[R] = r;
|
||
|
svertex[G] = g;
|
||
|
svertex[B] = b;
|
||
|
svertex[A] = a;
|
||
|
|
||
|
svertex = spolygon.vertices[1];
|
||
|
svertex[X] = x + strokeWidth2;
|
||
|
svertex[Y] = y - strokeWidth2;
|
||
|
svertex[Z] = z;
|
||
|
|
||
|
svertex = spolygon.vertices[2];
|
||
|
svertex[X] = x + strokeWidth2;
|
||
|
svertex[Y] = y + strokeWidth2;
|
||
|
svertex[Z] = z;
|
||
|
|
||
|
svertex = spolygon.vertices[3];
|
||
|
svertex[X] = x - strokeWidth2;
|
||
|
svertex[Y] = y + strokeWidth2;
|
||
|
svertex[Z] = z;
|
||
|
|
||
|
spolygon.render();
|
||
|
}
|
||
|
|
||
|
|
||
|
// new bresenham clipping code, as old one was buggy [toxi]
|
||
|
private void thin_flat_line(int x1, int y1, int x2, int y2) {
|
||
|
int nx1,ny1,nx2,ny2;
|
||
|
|
||
|
// get the "dips" for the points to clip
|
||
|
int code1 = thin_flat_lineClipCode(x1, y1);
|
||
|
int code2 = thin_flat_lineClipCode(x2, y2);
|
||
|
|
||
|
if ((code1 & code2)!=0) {
|
||
|
return;
|
||
|
} else {
|
||
|
int dip = code1 | code2;
|
||
|
if (dip != 0) {
|
||
|
// now calculate the clipped points
|
||
|
float a1 = 0, a2 = 1, a = 0;
|
||
|
for (int i=0;i<4;i++) {
|
||
|
if (((dip>>i)%2)==1) {
|
||
|
a = thin_flat_lineSlope((float)x1, (float)y1,
|
||
|
(float)x2, (float)y2, i+1);
|
||
|
if (((code1>>i)%2)==1) {
|
||
|
a1 = (float)Math.max(a, a1);
|
||
|
} else {
|
||
|
a2 = (float)Math.min(a, a2);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if (a1>a2) return;
|
||
|
else {
|
||
|
nx1=(int) (x1+a1*(x2-x1));
|
||
|
ny1=(int) (y1+a1*(y2-y1));
|
||
|
nx2=(int) (x1+a2*(x2-x1));
|
||
|
ny2=(int) (y1+a2*(y2-y1));
|
||
|
}
|
||
|
// line is fully visible/unclipped
|
||
|
} else {
|
||
|
nx1=x1; nx2=x2;
|
||
|
ny1=y1; ny2=y2;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// new "extremely fast" line code
|
||
|
// adapted from http://www.edepot.com/linee.html
|
||
|
|
||
|
boolean yLonger=false;
|
||
|
int shortLen=ny2-ny1;
|
||
|
int longLen=nx2-nx1;
|
||
|
if (Math.abs(shortLen)>Math.abs(longLen)) {
|
||
|
int swap=shortLen;
|
||
|
shortLen=longLen;
|
||
|
longLen=swap;
|
||
|
yLonger=true;
|
||
|
}
|
||
|
int decInc;
|
||
|
if (longLen==0) decInc=0;
|
||
|
else decInc = (shortLen << 16) / longLen;
|
||
|
|
||
|
if (nx1==nx2) {
|
||
|
// special case: vertical line
|
||
|
if (ny1>ny2) { int ty=ny1; ny1=ny2; ny2=ty; }
|
||
|
int offset=ny1*width+nx1;
|
||
|
for(int j=ny1; j<=ny2; j++) {
|
||
|
thin_pointAtIndex(offset,0,strokeColor);
|
||
|
offset+=width;
|
||
|
}
|
||
|
return;
|
||
|
} else if (ny1==ny2) {
|
||
|
// special case: horizontal line
|
||
|
if (nx1>nx2) { int tx=nx1; nx1=nx2; nx2=tx; }
|
||
|
int offset=ny1*width+nx1;
|
||
|
for(int j=nx1; j<=nx2; j++) thin_pointAtIndex(offset++,0,strokeColor);
|
||
|
return;
|
||
|
} else if (yLonger) {
|
||
|
if (longLen>0) {
|
||
|
longLen+=ny1;
|
||
|
for (int j=0x8000+(nx1<<16);ny1<=longLen;++ny1) {
|
||
|
thin_pointAt(j>>16, ny1, 0, strokeColor);
|
||
|
j+=decInc;
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
longLen+=ny1;
|
||
|
for (int j=0x8000+(nx1<<16);ny1>=longLen;--ny1) {
|
||
|
thin_pointAt(j>>16, ny1, 0, strokeColor);
|
||
|
j-=decInc;
|
||
|
}
|
||
|
return;
|
||
|
} else if (longLen>0) {
|
||
|
longLen+=nx1;
|
||
|
for (int j=0x8000+(ny1<<16);nx1<=longLen;++nx1) {
|
||
|
thin_pointAt(nx1, j>>16, 0, strokeColor);
|
||
|
j+=decInc;
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
longLen+=nx1;
|
||
|
for (int j=0x8000+(ny1<<16);nx1>=longLen;--nx1) {
|
||
|
thin_pointAt(nx1, j>>16, 0, strokeColor);
|
||
|
j-=decInc;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private int thin_flat_lineClipCode(float x, float y) {
|
||
|
return ((y < 0 ? 8 : 0) | (y > height1 ? 4 : 0) |
|
||
|
(x < 0 ? 2 : 0) | (x > width1 ? 1 : 0));
|
||
|
}
|
||
|
|
||
|
private float thin_flat_lineSlope(float x1, float y1,
|
||
|
float x2, float y2, int border) {
|
||
|
switch (border) {
|
||
|
case 4: {
|
||
|
return (-y1)/(y2-y1);
|
||
|
}
|
||
|
case 3: {
|
||
|
return (height1-y1)/(y2-y1);
|
||
|
}
|
||
|
case 2: {
|
||
|
return (-x1)/(x2-x1);
|
||
|
}
|
||
|
case 1: {
|
||
|
return (width1-x1)/(x2-x1);
|
||
|
}
|
||
|
}
|
||
|
return -1f;
|
||
|
}
|
||
|
|
||
|
|
||
|
private boolean flat_line_retribution(float x1, float y1,
|
||
|
float x2, float y2,
|
||
|
float r1, float g1, float b1) {
|
||
|
/*
|
||
|
// assume that if it is/isn't big in one dir, then the
|
||
|
// other doesn't matter, cuz that's a weird case
|
||
|
float lwidth = m00*strokeWeight + m01*strokeWeight;
|
||
|
//float lheight = m10*strokeWeight + m11*strokeWeight;
|
||
|
// lines of stroke thickness 1 can be anywhere from -1.41 to 1.41
|
||
|
if ((strokeWeight < TWO) && (!hints[SCALE_STROKE_WIDTH])) {
|
||
|
//if (abs(lwidth) < 1.5f) {
|
||
|
//System.out.println("flat line retribution " + r1 + " " + g1 + " " + b1);
|
||
|
int strokeSaved = strokeColor;
|
||
|
strokeColor = float_color(r1, g1, b1);
|
||
|
thin_flat_line((int)x1, (int)y1, (int)x2, (int)y2);
|
||
|
strokeColor = strokeSaved;
|
||
|
return true;
|
||
|
}
|
||
|
*/
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
|
||
|
private void thick_flat_line(float ox1, float oy1,
|
||
|
float r1, float g1, float b1, float a1,
|
||
|
float ox2, float oy2,
|
||
|
float r2, float g2, float b2, float a2) {
|
||
|
spolygon.interpARGB = (r1 != r2) || (g1 != g2) || (b1 != b2) || (a1 != a2);
|
||
|
spolygon.interpZ = false;
|
||
|
|
||
|
if (!spolygon.interpARGB &&
|
||
|
flat_line_retribution(ox1, oy1, ox2, oy2, r1, g1, b1)) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
float dX = ox2-ox1 + EPSILON;
|
||
|
float dY = oy2-oy1 + EPSILON;
|
||
|
float len = sqrt(dX*dX + dY*dY);
|
||
|
|
||
|
// TODO strokeWidth should be transformed!
|
||
|
float rh = strokeWeight / len;
|
||
|
|
||
|
float dx0 = rh * dY;
|
||
|
float dy0 = rh * dX;
|
||
|
float dx1 = rh * dY;
|
||
|
float dy1 = rh * dX;
|
||
|
|
||
|
spolygon.reset(4);
|
||
|
|
||
|
float svertex[] = spolygon.vertices[0];
|
||
|
svertex[X] = ox1+dx0;
|
||
|
svertex[Y] = oy1-dy0;
|
||
|
svertex[R] = r1;
|
||
|
svertex[G] = g1;
|
||
|
svertex[B] = b1;
|
||
|
svertex[A] = a1;
|
||
|
|
||
|
svertex = spolygon.vertices[1];
|
||
|
svertex[X] = ox1-dx0;
|
||
|
svertex[Y] = oy1+dy0;
|
||
|
svertex[R] = r1;
|
||
|
svertex[G] = g1;
|
||
|
svertex[B] = b1;
|
||
|
svertex[A] = a1;
|
||
|
|
||
|
svertex = spolygon.vertices[2];
|
||
|
svertex[X] = ox2-dx1;
|
||
|
svertex[Y] = oy2+dy1;
|
||
|
svertex[R] = r2;
|
||
|
svertex[G] = g2;
|
||
|
svertex[B] = b2;
|
||
|
svertex[A] = a2;
|
||
|
|
||
|
svertex = spolygon.vertices[3];
|
||
|
svertex[X] = ox2+dx1;
|
||
|
svertex[Y] = oy2-dy1;
|
||
|
svertex[R] = r2;
|
||
|
svertex[G] = g2;
|
||
|
svertex[B] = b2;
|
||
|
svertex[A] = a2;
|
||
|
|
||
|
spolygon.render();
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
// OPT version without z coords can save 8 multiplies and some other
|
||
|
private void spatial_line(float x1, float y1,
|
||
|
float r1, float g1, float b1,
|
||
|
float x2, float y2,
|
||
|
float r2, float g2, float b2) {
|
||
|
spatial_line(x1, y1, 0, r1, g1, b1,
|
||
|
x2, y2, 0, r2, g2, b2);
|
||
|
}
|
||
|
|
||
|
|
||
|
// the incoming values are transformed,
|
||
|
// and the colors have been calculated
|
||
|
|
||
|
private void spatial_line(float x1, float y1, float z1,
|
||
|
float r1, float g1, float b1,
|
||
|
float x2, float y2, float z2,
|
||
|
float r2, float g2, float b2) {
|
||
|
spolygon.interpARGB = (r1 != r2) || (g1 != g2) || (b1 != b2);
|
||
|
if (!spolygon.interpARGB &&
|
||
|
flat_line_retribution(x1, y1, x2, y2, r1, g1, b1)) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
spolygon.interpZ = true;
|
||
|
|
||
|
float ox1 = x1; float oy1 = y1; float oz1 = z1;
|
||
|
float ox2 = x2; float oy2 = y2; float oz2 = z2;
|
||
|
|
||
|
float dX = ox2-ox1 + 0.0001f;
|
||
|
float dY = oy2-oy1 + 0.0001f;
|
||
|
float len = sqrt(dX*dX + dY*dY);
|
||
|
|
||
|
//float x0 = m00*0 + m01*0 + m03;
|
||
|
|
||
|
float rh = strokeWeight / len;
|
||
|
|
||
|
float dx0 = rh * dY;
|
||
|
float dy0 = rh * dX;
|
||
|
float dx1 = rh * dY;
|
||
|
float dy1 = rh * dX;
|
||
|
|
||
|
spolygon.reset(4);
|
||
|
|
||
|
float svertex[] = spolygon.vertices[0];
|
||
|
svertex[X] = ox1+dx0;
|
||
|
svertex[Y] = oy1-dy0;
|
||
|
svertex[Z] = oz1;
|
||
|
svertex[R] = r1; //calcR1;
|
||
|
svertex[G] = g1; //calcG1;
|
||
|
svertex[B] = b1; //calcB1;
|
||
|
|
||
|
svertex = spolygon.vertices[1];
|
||
|
svertex[X] = ox1-dx0;
|
||
|
svertex[Y] = oy1+dy0;
|
||
|
svertex[Z] = oz1;
|
||
|
svertex[R] = r1; //calcR1;
|
||
|
svertex[G] = g1; //calcG1;
|
||
|
svertex[B] = b1; //calcB1;
|
||
|
|
||
|
svertex = spolygon.vertices[2];
|
||
|
svertex[X] = ox2-dx1;
|
||
|
svertex[Y] = oy2+dy1;
|
||
|
svertex[Z] = oz2;
|
||
|
svertex[R] = r2; //calcR2;
|
||
|
svertex[G] = g2; //calcG2;
|
||
|
svertex[B] = b2; //calcB2;
|
||
|
|
||
|
svertex = spolygon.vertices[3];
|
||
|
svertex[X] = ox2+dx1;
|
||
|
svertex[Y] = oy2-dy1;
|
||
|
svertex[Z] = oz2;
|
||
|
svertex[R] = r2; //calcR2;
|
||
|
svertex[G] = g2; //calcG2;
|
||
|
svertex[B] = b2; //calcB2;
|
||
|
|
||
|
spolygon.render();
|
||
|
}
|
||
|
*/
|
||
|
|
||
|
|
||
|
// max is what to count to
|
||
|
// offset is offset to the 'next' vertex
|
||
|
// increment is how much to increment in the loop
|
||
|
private void draw_lines(float vertices[][], int max,
|
||
|
int offset, int increment, int skip) {
|
||
|
|
||
|
if (strokeWeight < 2) {
|
||
|
for (int i = 0; i < max; i += increment) {
|
||
|
if ((skip != 0) && (((i+offset) % skip) == 0)) continue;
|
||
|
|
||
|
float a[] = vertices[i];
|
||
|
float b[] = vertices[i+offset];
|
||
|
|
||
|
if (line == null) line = new PLine(this);
|
||
|
|
||
|
line.reset();
|
||
|
line.setIntensities(a[SR], a[SG], a[SB], a[SA],
|
||
|
b[SR], b[SG], b[SB], b[SA]);
|
||
|
line.setVertices(a[X], a[Y], a[Z],
|
||
|
b[X], b[Y], b[Z]);
|
||
|
line.draw();
|
||
|
}
|
||
|
|
||
|
} else { // use old line code for thickness > 1
|
||
|
|
||
|
if ((strokeWeight < 2) && !strokeChanged) {
|
||
|
// need to set color at least once?
|
||
|
|
||
|
// THIS PARTICULAR CASE SHOULD NO LONGER BE REACHABLE
|
||
|
|
||
|
for (int i = 0; i < max; i += increment) {
|
||
|
if ((skip != 0) && (((i+offset) % skip) == 0)) continue;
|
||
|
thin_flat_line((int) vertices[i][X],
|
||
|
(int) vertices[i][Y],
|
||
|
(int) vertices[i+offset][X],
|
||
|
(int) vertices[i+offset][Y]);
|
||
|
}
|
||
|
} else {
|
||
|
for (int i = 0; i < max; i += increment) {
|
||
|
if ((skip != 0) && (((i+offset) % skip) == 0)) continue;
|
||
|
float v1[] = vertices[i];
|
||
|
float v2[] = vertices[i+offset];
|
||
|
thick_flat_line(v1[X], v1[Y], v1[SR], v1[SG], v1[SB], v1[SA],
|
||
|
v2[X], v2[Y], v2[SR], v2[SG], v2[SB], v2[SA]);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
//////////////////////////////////////////////////////////////
|
||
|
|
||
|
// UGLY RENDERING SHIT
|
||
|
|
||
|
|
||
|
private void thin_point(int x, int y, float z, int color) {
|
||
|
// necessary? [fry] yes! [toxi]
|
||
|
if (x<0 || x>width1 || y<0 || y>height1) return;
|
||
|
|
||
|
int index = y*width + x;
|
||
|
if ((color & 0xff000000) == 0xff000000) { // opaque
|
||
|
pixels[index] = color;
|
||
|
|
||
|
} else { // transparent
|
||
|
// couldn't seem to get this working correctly
|
||
|
|
||
|
//pixels[index] = _blend(pixels[index],
|
||
|
// color & 0xffffff, (color >> 24) & 0xff);
|
||
|
|
||
|
// a1 is how much of the orig pixel
|
||
|
int a2 = (color >> 24) & 0xff;
|
||
|
int a1 = a2 ^ 0xff;
|
||
|
|
||
|
int p2 = strokeColor;
|
||
|
int p1 = pixels[index];
|
||
|
|
||
|
int r = (a1 * ((p1 >> 16) & 0xff) + a2 * ((p2 >> 16) & 0xff)) & 0xff00;
|
||
|
int g = (a1 * ((p1 >> 8) & 0xff) + a2 * ((p2 >> 8) & 0xff)) & 0xff00;
|
||
|
int b = (a1 * ( p1 & 0xff) + a2 * ( p2 & 0xff)) >> 8;
|
||
|
|
||
|
pixels[index] = 0xff000000 | (r << 8) | g | b;
|
||
|
|
||
|
//pixels[index] = _blend(pixels[index],
|
||
|
// color & 0xffffff, (color >> 24) & 0xff);
|
||
|
/*
|
||
|
pixels[index] = 0xff000000 |
|
||
|
((((a1 * ((pixels[index] >> 16) & 0xff) +
|
||
|
a2 * ((color >> 16) & 0xff)) & 0xff00) << 24) << 8) |
|
||
|
(((a1 * ((pixels[index] >> 8) & 0xff) +
|
||
|
a2 * ((color >> 8) & 0xff)) & 0xff00) << 16) |
|
||
|
(((a1 * ( pixels[index] & 0xff) +
|
||
|
a2 * ( color & 0xff)) >> 8));
|
||
|
*/
|
||
|
}
|
||
|
zbuffer[index] = z;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
//////////////////////////////////////////////////////////////
|
||
|
|
||
|
// BACKGROUND AND FRIENDS
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Clear the pixel buffer.
|
||
|
*/
|
||
|
protected void clear() {
|
||
|
for (int i = 0; i < pixelCount; i++) {
|
||
|
pixels[i] = backgroundColor;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
//////////////////////////////////////////////////////////////
|
||
|
|
||
|
// INTERNAL SCHIZZLE
|
||
|
|
||
|
|
||
|
private boolean untransformed() {
|
||
|
return ((m00 == 1) && (m01 == 0) && (m02 == 0) &&
|
||
|
(m10 == 0) && (m11 == 1) && (m12 == 0));
|
||
|
}
|
||
|
|
||
|
|
||
|
private boolean unwarped() {
|
||
|
return ((m00 == 1) && (m01 == 0) && (m10 == 0) && (m11 == 1));
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
// doesn't really do lighting per se...
|
||
|
private void calc_lighting(float r, float g, float b,
|
||
|
float ix, float iy, float iz,
|
||
|
float nx, float ny, float nz,
|
||
|
float target[], int toffset) {
|
||
|
target[toffset + 0] = r;
|
||
|
target[toffset + 1] = g;
|
||
|
target[toffset + 2] = b;
|
||
|
}
|
||
|
|
||
|
|
||
|
static private final int float_color(float r, float g, float b) {
|
||
|
return (0xff000000 |
|
||
|
((int) (255.0f * r)) << 16 |
|
||
|
((int) (255.0f * g)) << 8 |
|
||
|
((int) (255.0f * b)));
|
||
|
}
|
||
|
|
||
|
public final static int _blend(int p1, int p2, int a2) {
|
||
|
// scale alpha by alpha of incoming pixel
|
||
|
a2 = (a2 * (p2 >>> 24)) >> 8;
|
||
|
|
||
|
int a1 = a2 ^ 0xff;
|
||
|
int r = (a1 * ((p1 >> 16) & 0xff) + a2 * ((p2 >> 16) & 0xff)) & 0xff00;
|
||
|
int g = (a1 * ((p1 >> 8) & 0xff) + a2 * ((p2 >> 8) & 0xff)) & 0xff00;
|
||
|
int b = (a1 * ( p1 & 0xff) + a2 * ( p2 & 0xff)) >> 8;
|
||
|
|
||
|
return 0xff000000 | (r << 8) | g | b;
|
||
|
}
|
||
|
}
|