From 37cdb40934cb3191fa2b73f7fbefb72a22cfe9c1 Mon Sep 17 00:00:00 2001 From: codeanticode Date: Fri, 20 Feb 2015 16:28:45 -0500 Subject: [PATCH] added jogl library --- java/libraries/jogl/.gitignore | 1 + .../jogl/examples/LargeStage/LargeStage.pde | 13 + .../examples/OffscreenTest/OffscreenTest.pde | 31 + .../jogl/examples/RestartTest/RestartTest.pde | 28 + java/libraries/jogl/library/export.txt | 10 + .../jogl/src/processing/jogl/PGraphics2D.java | 594 + .../src/processing/jogl/PGraphics2D2X.java | 595 + .../jogl/src/processing/jogl/PGraphics3D.java | 278 + .../src/processing/jogl/PGraphics3D2X.java | 279 + .../src/processing/jogl/PGraphicsOpenGL.java | 12278 ++++++++++++++++ .../jogl/src/processing/jogl/PJOGL.java | 2624 ++++ .../src/processing/jogl/PSurfaceNEWT.java | 713 + 12 files changed, 17444 insertions(+) create mode 100644 java/libraries/jogl/.gitignore create mode 100644 java/libraries/jogl/examples/LargeStage/LargeStage.pde create mode 100644 java/libraries/jogl/examples/OffscreenTest/OffscreenTest.pde create mode 100644 java/libraries/jogl/examples/RestartTest/RestartTest.pde create mode 100644 java/libraries/jogl/library/export.txt create mode 100644 java/libraries/jogl/src/processing/jogl/PGraphics2D.java create mode 100644 java/libraries/jogl/src/processing/jogl/PGraphics2D2X.java create mode 100644 java/libraries/jogl/src/processing/jogl/PGraphics3D.java create mode 100644 java/libraries/jogl/src/processing/jogl/PGraphics3D2X.java create mode 100644 java/libraries/jogl/src/processing/jogl/PGraphicsOpenGL.java create mode 100644 java/libraries/jogl/src/processing/jogl/PJOGL.java create mode 100644 java/libraries/jogl/src/processing/jogl/PSurfaceNEWT.java diff --git a/java/libraries/jogl/.gitignore b/java/libraries/jogl/.gitignore new file mode 100644 index 000000000..ba077a403 --- /dev/null +++ b/java/libraries/jogl/.gitignore @@ -0,0 +1 @@ +bin diff --git a/java/libraries/jogl/examples/LargeStage/LargeStage.pde b/java/libraries/jogl/examples/LargeStage/LargeStage.pde new file mode 100644 index 000000000..0e69acdd3 --- /dev/null +++ b/java/libraries/jogl/examples/LargeStage/LargeStage.pde @@ -0,0 +1,13 @@ +import processing.jogl.*; + +void setup() { + size(4000, 2000, JOGL.P3D); +} + +void draw() { + background(255, 0, 0); + line(0, 0, width, height); + line(0, height, width, 0); + println(mouseX, mouseY); + ellipse(mouseX, mouseY, 50, 50); +} \ No newline at end of file diff --git a/java/libraries/jogl/examples/OffscreenTest/OffscreenTest.pde b/java/libraries/jogl/examples/OffscreenTest/OffscreenTest.pde new file mode 100644 index 000000000..e793b9a56 --- /dev/null +++ b/java/libraries/jogl/examples/OffscreenTest/OffscreenTest.pde @@ -0,0 +1,31 @@ +import processing.jogl.*; + +PGraphics pg; + +void setup() { + size(400, 400, JOGL.P2D); + + pg = createGraphics(400, 400, JOGL.P2D); + pg.smooth(4); +} + +void draw() { + background(0); + + pg.beginDraw(); + pg.background(255, 0, 0); + pg.ellipse(mouseX, mouseY, 100, 100); + pg.endDraw(); + + image(pg, 0, 0, 400, 400); +} + +void keyPressed() { + if (key == '1') pg.smooth(1); + else if (key == '2') pg.smooth(2); + else if (key == '3') pg.smooth(4); + else if (key == '4') pg.smooth(8); + else if (key == '5') pg.smooth(16); + else if (key == '6') pg.smooth(32); +} + diff --git a/java/libraries/jogl/examples/RestartTest/RestartTest.pde b/java/libraries/jogl/examples/RestartTest/RestartTest.pde new file mode 100644 index 000000000..7c18cbd56 --- /dev/null +++ b/java/libraries/jogl/examples/RestartTest/RestartTest.pde @@ -0,0 +1,28 @@ +import processing.jogl.*; + +PShape cube; + +void setup() { + size(400, 400, JOGL.P3D); + smooth(); + + cube = createShape(BOX, 100); +} + +void draw() { + background(120); + + lights(); + + translate(mouseX, mouseY); + rotateX(frameCount * 0.01f); + rotateY(frameCount * 0.01f); + + shape(cube); +} + +void keyPressed() { + // Changing the smooth configuration restarts the OpenGL surface. + // Automatically recreates all the current GL resources. + noSmooth(); +} diff --git a/java/libraries/jogl/library/export.txt b/java/libraries/jogl/library/export.txt new file mode 100644 index 000000000..308d1a35a --- /dev/null +++ b/java/libraries/jogl/library/export.txt @@ -0,0 +1,10 @@ +# If you want to support more platforms, visit jogamp.org to get the +# natives libraries for the platform in question (i.e. Solaris). + +name = OpenGL + +application.macosx=core.jar,jogl-all.jar,gluegen-rt.jar,jogl-all-natives-macosx-universal.jar,gluegen-rt-natives-macosx-universal.jar +application.windows32=core.jar,jogl-all.jar,gluegen-rt.jar,jogl-all-natives-windows-i586.jar,gluegen-rt-natives-windows-i586.jar +application.windows64=core.jar,jogl-all.jar,gluegen-rt.jar,jogl-all-natives-windows-amd64.jar,gluegen-rt-natives-windows-amd64.jar +application.linux32=core.jar,jogl-all.jar,gluegen-rt.jar,jogl-all-natives-linux-i586.jar,gluegen-rt-natives-linux-i586.jar +application.linux64=core.jar,jogl-all.jar,gluegen-rt.jar,jogl-all-natives-linux-amd64.jar,gluegen-rt-natives-linux-amd64.jar diff --git a/java/libraries/jogl/src/processing/jogl/PGraphics2D.java b/java/libraries/jogl/src/processing/jogl/PGraphics2D.java new file mode 100644 index 000000000..1812c111e --- /dev/null +++ b/java/libraries/jogl/src/processing/jogl/PGraphics2D.java @@ -0,0 +1,594 @@ +/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2012 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 version 2.1 as published by the Free Software Foundation. + + 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.opengl; + +import java.io.InputStream; +import java.util.zip.GZIPInputStream; + +import processing.core.PApplet; +import processing.core.PConstants; +import processing.core.PGraphics; +import processing.core.PMatrix3D; +import processing.core.PShape; +import processing.core.PShapeSVG; +import processing.data.XML; + +public class PGraphics2D extends PGraphicsOpenGL { + + public PGraphics2D() { + super(); + } + + + ////////////////////////////////////////////////////////////// + + // RENDERER SUPPORT QUERIES + + + @Override + public boolean is2D() { + return true; + } + + + @Override + public boolean is3D() { + return false; + } + + + ////////////////////////////////////////////////////////////// + + // HINTS + + + @Override + public void hint(int which) { + if (which == ENABLE_STROKE_PERSPECTIVE) { + showWarning("Strokes cannot be perspective-corrected in 2D."); + return; + } + super.hint(which); + } + + + ////////////////////////////////////////////////////////////// + + // PROJECTION + + + @Override + public void ortho() { + showMethodWarning("ortho"); + } + + + @Override + public void ortho(float left, float right, + float bottom, float top) { + showMethodWarning("ortho"); + } + + + @Override + public void ortho(float left, float right, + float bottom, float top, + float near, float far) { + showMethodWarning("ortho"); + } + + + @Override + public void perspective() { + showMethodWarning("perspective"); + } + + + @Override + public void perspective(float fov, float aspect, float zNear, float zFar) { + showMethodWarning("perspective"); + } + + + @Override + public void frustum(float left, float right, float bottom, float top, + float znear, float zfar) { + showMethodWarning("frustum"); + } + + + @Override + protected void defaultPerspective() { + super.ortho(0, width, -height, 0, -1, +1); + } + + + ////////////////////////////////////////////////////////////// + + // CAMERA + + + @Override + public void beginCamera() { + showMethodWarning("beginCamera"); + } + + + @Override + public void endCamera() { + showMethodWarning("endCamera"); + } + + + @Override + public void camera() { + showMethodWarning("camera"); + } + + + @Override + public void camera(float eyeX, float eyeY, float eyeZ, + float centerX, float centerY, float centerZ, + float upX, float upY, float upZ) { + showMethodWarning("camera"); + } + + + @Override + protected void defaultCamera() { + cameraEyeX = cameraEyeY = cameraEyeZ = 0; + resetMatrix(); + } + + + ////////////////////////////////////////////////////////////// + + // MATRIX MORE! + + + @Override + protected void begin2D() { + pushProjection(); + defaultPerspective(); + pushMatrix(); + defaultCamera(); + } + + + @Override + protected void end2D() { + popMatrix(); + popProjection(); + } + + + ////////////////////////////////////////////////////////////// + + // SHAPE + + + @Override + public void shape(PShape shape) { + if (shape.is2D()) { + super.shape(shape); + } else { + showWarning("The shape object is not 2D, cannot be displayed with " + + "this renderer"); + } + } + + + @Override + public void shape(PShape shape, float x, float y) { + if (shape.is2D()) { + super.shape(shape, x, y); + } else { + showWarning("The shape object is not 2D, cannot be displayed with " + + "this renderer"); + } + } + + + @Override + public void shape(PShape shape, float a, float b, float c, float d) { + if (shape.is2D()) { + super.shape(shape, a, b, c, d); + } else { + showWarning("The shape object is not 2D, cannot be displayed with " + + "this renderer"); + } + } + + + @Override + public void shape(PShape shape, float x, float y, float z) { + showDepthWarningXYZ("shape"); + } + + + @Override + public void shape(PShape shape, float x, float y, float z, + float c, float d, float e) { + showDepthWarningXYZ("shape"); + } + + + ////////////////////////////////////////////////////////////// + + // SHAPE I/O + + + static protected boolean isSupportedExtension(String extension) { + return extension.equals("svg") || extension.equals("svgz"); + } + + + static protected PShape loadShapeImpl(PGraphics pg, String filename, + String extension) { + PShapeSVG svg = null; + + if (extension.equals("svg")) { + svg = new PShapeSVG(pg.parent.loadXML(filename)); + + } else if (extension.equals("svgz")) { + try { + InputStream input = + new GZIPInputStream(pg.parent.createInput(filename)); + XML xml = new XML(PApplet.createReader(input)); + svg = new PShapeSVG(xml); + } catch (Exception e) { + e.printStackTrace(); + } + } + + if (svg != null) { + PShapeOpenGL p2d = PShapeOpenGL.createShape2D((PGraphicsOpenGL)pg, svg); + return p2d; + } else { + return null; + } + } + + + ////////////////////////////////////////////////////////////// + + // SHAPE CREATION + + + @Override + public PShape createShape(PShape source) { + return PShapeOpenGL.createShape2D(this, source); + } + + + @Override + public PShape createShape() { + return createShape(PShape.GEOMETRY); + } + + + @Override + public PShape createShape(int type) { + return createShapeImpl(this, type); + } + + + @Override + public PShape createShape(int kind, float... p) { + return createShapeImpl(this, kind, p); + } + + + static protected PShapeOpenGL createShapeImpl(PGraphicsOpenGL pg, int type) { + PShapeOpenGL shape = null; + if (type == PConstants.GROUP) { + shape = new PShapeOpenGL(pg, PConstants.GROUP); + } else if (type == PShape.PATH) { + shape = new PShapeOpenGL(pg, PShape.PATH); + } else if (type == PShape.GEOMETRY) { + shape = new PShapeOpenGL(pg, PShape.GEOMETRY); + } + shape.set3D(false); + return shape; + } + + + static protected PShapeOpenGL createShapeImpl(PGraphicsOpenGL pg, + int kind, float... p) { + PShapeOpenGL shape = null; + int len = p.length; + + if (kind == POINT) { + if (len != 2) { + showWarning("Wrong number of parameters"); + return null; + } + shape = new PShapeOpenGL(pg, PShape.PRIMITIVE); + shape.setKind(POINT); + } else if (kind == LINE) { + if (len != 4) { + showWarning("Wrong number of parameters"); + return null; + } + shape = new PShapeOpenGL(pg, PShape.PRIMITIVE); + shape.setKind(LINE); + } else if (kind == TRIANGLE) { + if (len != 6) { + showWarning("Wrong number of parameters"); + return null; + } + shape = new PShapeOpenGL(pg, PShape.PRIMITIVE); + shape.setKind(TRIANGLE); + } else if (kind == QUAD) { + if (len != 8) { + showWarning("Wrong number of parameters"); + return null; + } + shape = new PShapeOpenGL(pg, PShape.PRIMITIVE); + shape.setKind(QUAD); + } else if (kind == RECT) { + if (len != 4 && len != 5 && len != 8 && len != 9) { + showWarning("Wrong number of parameters"); + return null; + } + shape = new PShapeOpenGL(pg, PShape.PRIMITIVE); + shape.setKind(RECT); + } else if (kind == ELLIPSE) { + if (len != 4 && len != 5) { + showWarning("Wrong number of parameters"); + return null; + } + shape = new PShapeOpenGL(pg, PShape.PRIMITIVE); + shape.setKind(ELLIPSE); + } else if (kind == ARC) { + if (len != 6 && len != 7) { + showWarning("Wrong number of parameters"); + return null; + } + shape = new PShapeOpenGL(pg, PShape.PRIMITIVE); + shape.setKind(ARC); + } else if (kind == BOX) { + showWarning("Primitive not supported in 2D"); + } else if (kind == SPHERE) { + showWarning("Primitive not supported in 2D"); + } else { + showWarning("Unrecognized primitive type"); + } + + if (shape != null) { + shape.setParams(p); + } + + shape.set3D(false); + return shape; + } + + + ////////////////////////////////////////////////////////////// + + // BEZIER VERTICES + + + @Override + public void bezierVertex(float x2, float y2, float z2, + float x3, float y3, float z3, + float x4, float y4, float z4) { + showDepthWarningXYZ("bezierVertex"); + } + + + ////////////////////////////////////////////////////////////// + + // QUADRATIC BEZIER VERTICES + + + @Override + public void quadraticVertex(float x2, float y2, float z2, + float x4, float y4, float z4) { + showDepthWarningXYZ("quadVertex"); + } + + + ////////////////////////////////////////////////////////////// + + // CURVE VERTICES + + + @Override + public void curveVertex(float x, float y, float z) { + showDepthWarningXYZ("curveVertex"); + } + + + ////////////////////////////////////////////////////////////// + + // BOX + + + @Override + public void box(float w, float h, float d) { + showMethodWarning("box"); + } + + + ////////////////////////////////////////////////////////////// + + // SPHERE + + + @Override + public void sphere(float r) { + showMethodWarning("sphere"); + } + + + ////////////////////////////////////////////////////////////// + + // VERTEX SHAPES + + + @Override + public void vertex(float x, float y, float z) { + showDepthWarningXYZ("vertex"); + } + + @Override + public void vertex(float x, float y, float z, float u, float v) { + showDepthWarningXYZ("vertex"); + } + + ////////////////////////////////////////////////////////////// + + // MATRIX TRANSFORMATIONS + + @Override + public void translate(float tx, float ty, float tz) { + showDepthWarningXYZ("translate"); + } + + @Override + public void rotateX(float angle) { + showDepthWarning("rotateX"); + } + + @Override + public void rotateY(float angle) { + showDepthWarning("rotateY"); + } + + @Override + public void rotateZ(float angle) { + showDepthWarning("rotateZ"); + } + + @Override + public void rotate(float angle, float vx, float vy, float vz) { + showVariationWarning("rotate"); + } + + @Override + public void applyMatrix(PMatrix3D source) { + showVariationWarning("applyMatrix"); + } + + @Override + public void applyMatrix(float n00, float n01, float n02, float n03, + float n10, float n11, float n12, float n13, + float n20, float n21, float n22, float n23, + float n30, float n31, float n32, float n33) { + showVariationWarning("applyMatrix"); + } + + @Override + public void scale(float sx, float sy, float sz) { + showDepthWarningXYZ("scale"); + } + + ////////////////////////////////////////////////////////////// + + // SCREEN AND MODEL COORDS + + @Override + public float screenX(float x, float y, float z) { + showDepthWarningXYZ("screenX"); + return 0; + } + + @Override + public float screenY(float x, float y, float z) { + showDepthWarningXYZ("screenY"); + return 0; + } + + @Override + public float screenZ(float x, float y, float z) { + showDepthWarningXYZ("screenZ"); + return 0; + } + + @Override + public PMatrix3D getMatrix(PMatrix3D target) { + showVariationWarning("getMatrix"); + return target; + } + + @Override + public void setMatrix(PMatrix3D source) { + showVariationWarning("setMatrix"); + } + + ////////////////////////////////////////////////////////////// + + // LIGHTS + + @Override + public void lights() { + showMethodWarning("lights"); + } + + @Override + public void noLights() { + showMethodWarning("noLights"); + } + + @Override + public void ambientLight(float red, float green, float blue) { + showMethodWarning("ambientLight"); + } + + @Override + public void ambientLight(float red, float green, float blue, + float x, float y, float z) { + showMethodWarning("ambientLight"); + } + + @Override + public void directionalLight(float red, float green, float blue, + float nx, float ny, float nz) { + showMethodWarning("directionalLight"); + } + + @Override + public void pointLight(float red, float green, float blue, + float x, float y, float z) { + showMethodWarning("pointLight"); + } + + @Override + public void spotLight(float red, float green, float blue, + float x, float y, float z, + float nx, float ny, float nz, + float angle, float concentration) { + showMethodWarning("spotLight"); + } + + @Override + public void lightFalloff(float constant, float linear, float quadratic) { + showMethodWarning("lightFalloff"); + } + + @Override + public void lightSpecular(float v1, float v2, float v3) { + showMethodWarning("lightSpecular"); + } +} \ No newline at end of file diff --git a/java/libraries/jogl/src/processing/jogl/PGraphics2D2X.java b/java/libraries/jogl/src/processing/jogl/PGraphics2D2X.java new file mode 100644 index 000000000..dc765cbf9 --- /dev/null +++ b/java/libraries/jogl/src/processing/jogl/PGraphics2D2X.java @@ -0,0 +1,595 @@ +/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2012 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 version 2.1 as published by the Free Software Foundation. + + 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.opengl; + +import java.io.InputStream; +import java.util.zip.GZIPInputStream; + +import processing.core.PApplet; +import processing.core.PConstants; +import processing.core.PGraphics; +import processing.core.PMatrix3D; +import processing.core.PShape; +import processing.core.PShapeSVG; +import processing.data.XML; + +public class PGraphics2D2X extends PGraphicsOpenGL { + + public PGraphics2D2X() { + super(); + pixelFactor = 2; + } + + + ////////////////////////////////////////////////////////////// + + // RENDERER SUPPORT QUERIES + + + @Override + public boolean is2D() { + return true; + } + + + @Override + public boolean is3D() { + return false; + } + + + ////////////////////////////////////////////////////////////// + + // HINTS + + + @Override + public void hint(int which) { + if (which == ENABLE_STROKE_PERSPECTIVE) { + showWarning("Strokes cannot be perspective-corrected in 2D."); + return; + } + super.hint(which); + } + + + ////////////////////////////////////////////////////////////// + + // PROJECTION + + + @Override + public void ortho() { + showMethodWarning("ortho"); + } + + + @Override + public void ortho(float left, float right, + float bottom, float top) { + showMethodWarning("ortho"); + } + + + @Override + public void ortho(float left, float right, + float bottom, float top, + float near, float far) { + showMethodWarning("ortho"); + } + + + @Override + public void perspective() { + showMethodWarning("perspective"); + } + + + @Override + public void perspective(float fov, float aspect, float zNear, float zFar) { + showMethodWarning("perspective"); + } + + + @Override + public void frustum(float left, float right, float bottom, float top, + float znear, float zfar) { + showMethodWarning("frustum"); + } + + + @Override + protected void defaultPerspective() { + super.ortho(0, width, -height, 0, -1, +1); + } + + + ////////////////////////////////////////////////////////////// + + // CAMERA + + + @Override + public void beginCamera() { + showMethodWarning("beginCamera"); + } + + + @Override + public void endCamera() { + showMethodWarning("endCamera"); + } + + + @Override + public void camera() { + showMethodWarning("camera"); + } + + + @Override + public void camera(float eyeX, float eyeY, float eyeZ, + float centerX, float centerY, float centerZ, + float upX, float upY, float upZ) { + showMethodWarning("camera"); + } + + + @Override + protected void defaultCamera() { + cameraEyeX = cameraEyeY = cameraEyeZ = 0; + resetMatrix(); + } + + + ////////////////////////////////////////////////////////////// + + // MATRIX MORE! + + + @Override + protected void begin2D() { + pushProjection(); + defaultPerspective(); + pushMatrix(); + defaultCamera(); + } + + + @Override + protected void end2D() { + popMatrix(); + popProjection(); + } + + + ////////////////////////////////////////////////////////////// + + // SHAPE + + + @Override + public void shape(PShape shape) { + if (shape.is2D()) { + super.shape(shape); + } else { + showWarning("The shape object is not 2D, cannot be displayed with " + + "this renderer"); + } + } + + + @Override + public void shape(PShape shape, float x, float y) { + if (shape.is2D()) { + super.shape(shape, x, y); + } else { + showWarning("The shape object is not 2D, cannot be displayed with " + + "this renderer"); + } + } + + + @Override + public void shape(PShape shape, float a, float b, float c, float d) { + if (shape.is2D()) { + super.shape(shape, a, b, c, d); + } else { + showWarning("The shape object is not 2D, cannot be displayed with " + + "this renderer"); + } + } + + + @Override + public void shape(PShape shape, float x, float y, float z) { + showDepthWarningXYZ("shape"); + } + + + @Override + public void shape(PShape shape, float x, float y, float z, + float c, float d, float e) { + showDepthWarningXYZ("shape"); + } + + + ////////////////////////////////////////////////////////////// + + // SHAPE I/O + + + static protected boolean isSupportedExtension(String extension) { + return extension.equals("svg") || extension.equals("svgz"); + } + + + static protected PShape loadShapeImpl(PGraphics pg, String filename, + String extension) { + PShapeSVG svg = null; + + if (extension.equals("svg")) { + svg = new PShapeSVG(pg.parent.loadXML(filename)); + + } else if (extension.equals("svgz")) { + try { + InputStream input = + new GZIPInputStream(pg.parent.createInput(filename)); + XML xml = new XML(PApplet.createReader(input)); + svg = new PShapeSVG(xml); + } catch (Exception e) { + e.printStackTrace(); + } + } + + if (svg != null) { + PShapeOpenGL p2d = PShapeOpenGL.createShape2D((PGraphicsOpenGL)pg, svg); + return p2d; + } else { + return null; + } + } + + + ////////////////////////////////////////////////////////////// + + // SHAPE CREATION + + + @Override + public PShape createShape(PShape source) { + return PShapeOpenGL.createShape2D(this, source); + } + + + @Override + public PShape createShape() { + return createShape(PShape.GEOMETRY); + } + + + @Override + public PShape createShape(int type) { + return createShapeImpl(this, type); + } + + + @Override + public PShape createShape(int kind, float... p) { + return createShapeImpl(this, kind, p); + } + + + static protected PShapeOpenGL createShapeImpl(PGraphicsOpenGL pg, int type) { + PShapeOpenGL shape = null; + if (type == PConstants.GROUP) { + shape = new PShapeOpenGL(pg, PConstants.GROUP); + } else if (type == PShape.PATH) { + shape = new PShapeOpenGL(pg, PShape.PATH); + } else if (type == PShape.GEOMETRY) { + shape = new PShapeOpenGL(pg, PShape.GEOMETRY); + } + shape.set3D(false); + return shape; + } + + + static protected PShapeOpenGL createShapeImpl(PGraphicsOpenGL pg, + int kind, float... p) { + PShapeOpenGL shape = null; + int len = p.length; + + if (kind == POINT) { + if (len != 2) { + showWarning("Wrong number of parameters"); + return null; + } + shape = new PShapeOpenGL(pg, PShape.PRIMITIVE); + shape.setKind(POINT); + } else if (kind == LINE) { + if (len != 4) { + showWarning("Wrong number of parameters"); + return null; + } + shape = new PShapeOpenGL(pg, PShape.PRIMITIVE); + shape.setKind(LINE); + } else if (kind == TRIANGLE) { + if (len != 6) { + showWarning("Wrong number of parameters"); + return null; + } + shape = new PShapeOpenGL(pg, PShape.PRIMITIVE); + shape.setKind(TRIANGLE); + } else if (kind == QUAD) { + if (len != 8) { + showWarning("Wrong number of parameters"); + return null; + } + shape = new PShapeOpenGL(pg, PShape.PRIMITIVE); + shape.setKind(QUAD); + } else if (kind == RECT) { + if (len != 4 && len != 5 && len != 8 && len != 9) { + showWarning("Wrong number of parameters"); + return null; + } + shape = new PShapeOpenGL(pg, PShape.PRIMITIVE); + shape.setKind(RECT); + } else if (kind == ELLIPSE) { + if (len != 4 && len != 5) { + showWarning("Wrong number of parameters"); + return null; + } + shape = new PShapeOpenGL(pg, PShape.PRIMITIVE); + shape.setKind(ELLIPSE); + } else if (kind == ARC) { + if (len != 6 && len != 7) { + showWarning("Wrong number of parameters"); + return null; + } + shape = new PShapeOpenGL(pg, PShape.PRIMITIVE); + shape.setKind(ARC); + } else if (kind == BOX) { + showWarning("Primitive not supported in 2D"); + } else if (kind == SPHERE) { + showWarning("Primitive not supported in 2D"); + } else { + showWarning("Unrecognized primitive type"); + } + + if (shape != null) { + shape.setParams(p); + } + + shape.set3D(false); + return shape; + } + + + ////////////////////////////////////////////////////////////// + + // BEZIER VERTICES + + + @Override + public void bezierVertex(float x2, float y2, float z2, + float x3, float y3, float z3, + float x4, float y4, float z4) { + showDepthWarningXYZ("bezierVertex"); + } + + + ////////////////////////////////////////////////////////////// + + // QUADRATIC BEZIER VERTICES + + + @Override + public void quadraticVertex(float x2, float y2, float z2, + float x4, float y4, float z4) { + showDepthWarningXYZ("quadVertex"); + } + + + ////////////////////////////////////////////////////////////// + + // CURVE VERTICES + + + @Override + public void curveVertex(float x, float y, float z) { + showDepthWarningXYZ("curveVertex"); + } + + + ////////////////////////////////////////////////////////////// + + // BOX + + + @Override + public void box(float w, float h, float d) { + showMethodWarning("box"); + } + + + ////////////////////////////////////////////////////////////// + + // SPHERE + + + @Override + public void sphere(float r) { + showMethodWarning("sphere"); + } + + + ////////////////////////////////////////////////////////////// + + // VERTEX SHAPES + + + @Override + public void vertex(float x, float y, float z) { + showDepthWarningXYZ("vertex"); + } + + @Override + public void vertex(float x, float y, float z, float u, float v) { + showDepthWarningXYZ("vertex"); + } + + ////////////////////////////////////////////////////////////// + + // MATRIX TRANSFORMATIONS + + @Override + public void translate(float tx, float ty, float tz) { + showDepthWarningXYZ("translate"); + } + + @Override + public void rotateX(float angle) { + showDepthWarning("rotateX"); + } + + @Override + public void rotateY(float angle) { + showDepthWarning("rotateY"); + } + + @Override + public void rotateZ(float angle) { + showDepthWarning("rotateZ"); + } + + @Override + public void rotate(float angle, float vx, float vy, float vz) { + showVariationWarning("rotate"); + } + + @Override + public void applyMatrix(PMatrix3D source) { + showVariationWarning("applyMatrix"); + } + + @Override + public void applyMatrix(float n00, float n01, float n02, float n03, + float n10, float n11, float n12, float n13, + float n20, float n21, float n22, float n23, + float n30, float n31, float n32, float n33) { + showVariationWarning("applyMatrix"); + } + + @Override + public void scale(float sx, float sy, float sz) { + showDepthWarningXYZ("scale"); + } + + ////////////////////////////////////////////////////////////// + + // SCREEN AND MODEL COORDS + + @Override + public float screenX(float x, float y, float z) { + showDepthWarningXYZ("screenX"); + return 0; + } + + @Override + public float screenY(float x, float y, float z) { + showDepthWarningXYZ("screenY"); + return 0; + } + + @Override + public float screenZ(float x, float y, float z) { + showDepthWarningXYZ("screenZ"); + return 0; + } + + @Override + public PMatrix3D getMatrix(PMatrix3D target) { + showVariationWarning("getMatrix"); + return target; + } + + @Override + public void setMatrix(PMatrix3D source) { + showVariationWarning("setMatrix"); + } + + ////////////////////////////////////////////////////////////// + + // LIGHTS + + @Override + public void lights() { + showMethodWarning("lights"); + } + + @Override + public void noLights() { + showMethodWarning("noLights"); + } + + @Override + public void ambientLight(float red, float green, float blue) { + showMethodWarning("ambientLight"); + } + + @Override + public void ambientLight(float red, float green, float blue, + float x, float y, float z) { + showMethodWarning("ambientLight"); + } + + @Override + public void directionalLight(float red, float green, float blue, + float nx, float ny, float nz) { + showMethodWarning("directionalLight"); + } + + @Override + public void pointLight(float red, float green, float blue, + float x, float y, float z) { + showMethodWarning("pointLight"); + } + + @Override + public void spotLight(float red, float green, float blue, + float x, float y, float z, + float nx, float ny, float nz, + float angle, float concentration) { + showMethodWarning("spotLight"); + } + + @Override + public void lightFalloff(float constant, float linear, float quadratic) { + showMethodWarning("lightFalloff"); + } + + @Override + public void lightSpecular(float v1, float v2, float v3) { + showMethodWarning("lightSpecular"); + } +} \ No newline at end of file diff --git a/java/libraries/jogl/src/processing/jogl/PGraphics3D.java b/java/libraries/jogl/src/processing/jogl/PGraphics3D.java new file mode 100644 index 000000000..c37941c78 --- /dev/null +++ b/java/libraries/jogl/src/processing/jogl/PGraphics3D.java @@ -0,0 +1,278 @@ +/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2012 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 version 2.1 as published by the Free Software Foundation. + + 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.opengl; + +import java.io.InputStream; +import java.util.zip.GZIPInputStream; + +import processing.core.PApplet; +import processing.core.PConstants; +import processing.core.PGraphics; +import processing.core.PShape; +import processing.core.PShapeOBJ; + +public class PGraphics3D extends PGraphicsOpenGL { + + public PGraphics3D() { + super(); + } + + + ////////////////////////////////////////////////////////////// + + // RENDERER SUPPORT QUERIES + + + @Override + public boolean is2D() { + return false; + } + + + @Override + public boolean is3D() { + return true; + } + + + ////////////////////////////////////////////////////////////// + + // PROJECTION + + + @Override + protected void defaultPerspective() { + perspective(); + } + + + ////////////////////////////////////////////////////////////// + + // CAMERA + + + @Override + protected void defaultCamera() { + camera(); + } + + + ////////////////////////////////////////////////////////////// + + // MATRIX MORE! + + + @Override + protected void begin2D() { + pushProjection(); + ortho(0, width, 0, height, -1, +1); + pushMatrix(); + + // Set camera for 2D rendering, it simply centers at (width/2, height/2) + float centerX = width/2; + float centerY = height/2; + modelview.reset(); + modelview.translate(-centerX, -centerY); + + modelviewInv.set(modelview); + modelviewInv.invert(); + + camera.set(modelview); + cameraInv.set(modelviewInv); + + updateProjmodelview(); + } + + + @Override + protected void end2D() { + popMatrix(); + popProjection(); + } + + + ////////////////////////////////////////////////////////////// + + // SHAPE I/O + + + static protected boolean isSupportedExtension(String extension) { + return extension.equals("obj"); + } + + + static protected PShape loadShapeImpl(PGraphics pg, String filename, + String extension) { + PShapeOBJ obj = null; + + if (extension.equals("obj")) { + obj = new PShapeOBJ(pg.parent, filename); + + } else if (extension.equals("objz")) { + try { + // TODO: The obj file can be read from the gzip, but if it refers to + // a materials file and texture images, those must be contained in the + // data folder, cannot be inside the gzip. + InputStream input = + new GZIPInputStream(pg.parent.createInput(filename)); + obj = new PShapeOBJ(pg.parent, PApplet.createReader(input)); + } catch (Exception e) { + e.printStackTrace(); + } + } + + if (obj != null) { + int prevTextureMode = pg.textureMode; + pg.textureMode = NORMAL; + PShapeOpenGL p3d = PShapeOpenGL.createShape3D((PGraphicsOpenGL)pg, obj); + pg.textureMode = prevTextureMode; + return p3d; + } else { + return null; + } + } + + + ////////////////////////////////////////////////////////////// + + // SHAPE CREATION + + + @Override + public PShape createShape(PShape source) { + return PShapeOpenGL.createShape3D(this, source); + } + + + @Override + public PShape createShape() { + return createShape(PShape.GEOMETRY); + } + + + @Override + public PShape createShape(int type) { + return createShapeImpl(this, type); + } + + + @Override + public PShape createShape(int kind, float... p) { + return createShapeImpl(this, kind, p); + } + + + static protected PShapeOpenGL createShapeImpl(PGraphicsOpenGL pg, int type) { + PShapeOpenGL shape = null; + if (type == PConstants.GROUP) { + shape = new PShapeOpenGL(pg, PConstants.GROUP); + } else if (type == PShape.PATH) { + shape = new PShapeOpenGL(pg, PShape.PATH); + } else if (type == PShape.GEOMETRY) { + shape = new PShapeOpenGL(pg, PShape.GEOMETRY); + } + shape.set3D(true); + return shape; + } + + + static protected PShapeOpenGL createShapeImpl(PGraphicsOpenGL pg, + int kind, float... p) { + PShapeOpenGL shape = null; + int len = p.length; + + if (kind == POINT) { + if (len != 2 && len != 3) { + showWarning("Wrong number of parameters"); + return null; + } + shape = new PShapeOpenGL(pg, PShape.PRIMITIVE); + shape.setKind(POINT); + } else if (kind == LINE) { + if (len != 4 && len != 6) { + showWarning("Wrong number of parameters"); + return null; + } + shape = new PShapeOpenGL(pg, PShape.PRIMITIVE); + shape.setKind(LINE); + } else if (kind == TRIANGLE) { + if (len != 6) { + showWarning("Wrong number of parameters"); + return null; + } + shape = new PShapeOpenGL(pg, PShape.PRIMITIVE); + shape.setKind(TRIANGLE); + } else if (kind == QUAD) { + if (len != 8) { + showWarning("Wrong number of parameters"); + return null; + } + shape = new PShapeOpenGL(pg, PShape.PRIMITIVE); + shape.setKind(QUAD); + } else if (kind == RECT) { + if (len != 4 && len != 5 && len != 8 && len != 9) { + showWarning("Wrong number of parameters"); + return null; + } + shape = new PShapeOpenGL(pg, PShape.PRIMITIVE); + shape.setKind(RECT); + } else if (kind == ELLIPSE) { + if (len != 4 && len != 5) { + showWarning("Wrong number of parameters"); + return null; + } + shape = new PShapeOpenGL(pg, PShape.PRIMITIVE); + shape.setKind(ELLIPSE); + } else if (kind == ARC) { + if (len != 6 && len != 7) { + showWarning("Wrong number of parameters"); + return null; + } + shape = new PShapeOpenGL(pg, PShape.PRIMITIVE); + shape.setKind(ARC); + } else if (kind == BOX) { + if (len != 1 && len != 3) { + showWarning("Wrong number of parameters"); + return null; + } + shape = new PShapeOpenGL(pg, PShape.PRIMITIVE); + shape.setKind(BOX); + } else if (kind == SPHERE) { + if (len < 1 || 3 < len) { + showWarning("Wrong number of parameters"); + return null; + } + shape = new PShapeOpenGL(pg, PShape.PRIMITIVE); + shape.setKind(SPHERE); + } else { + showWarning("Unrecognized primitive type"); + } + + if (shape != null) { + shape.setParams(p); + } + + shape.set3D(true); + return shape; + } +} \ No newline at end of file diff --git a/java/libraries/jogl/src/processing/jogl/PGraphics3D2X.java b/java/libraries/jogl/src/processing/jogl/PGraphics3D2X.java new file mode 100644 index 000000000..a2cd7f376 --- /dev/null +++ b/java/libraries/jogl/src/processing/jogl/PGraphics3D2X.java @@ -0,0 +1,279 @@ +/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2012 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 version 2.1 as published by the Free Software Foundation. + + 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.opengl; + +import java.io.InputStream; +import java.util.zip.GZIPInputStream; + +import processing.core.PApplet; +import processing.core.PConstants; +import processing.core.PGraphics; +import processing.core.PShape; +import processing.core.PShapeOBJ; + +public class PGraphics3D2X extends PGraphicsOpenGL { + + public PGraphics3D2X() { + super(); + pixelFactor = 2; + } + + + ////////////////////////////////////////////////////////////// + + // RENDERER SUPPORT QUERIES + + + @Override + public boolean is2D() { + return false; + } + + + @Override + public boolean is3D() { + return true; + } + + + ////////////////////////////////////////////////////////////// + + // PROJECTION + + + @Override + protected void defaultPerspective() { + perspective(); + } + + + ////////////////////////////////////////////////////////////// + + // CAMERA + + + @Override + protected void defaultCamera() { + camera(); + } + + + ////////////////////////////////////////////////////////////// + + // MATRIX MORE! + + + @Override + protected void begin2D() { + pushProjection(); + ortho(0, width, 0, height, -1, +1); + pushMatrix(); + + // Set camera for 2D rendering, it simply centers at (width/2, height/2) + float centerX = width/2; + float centerY = height/2; + modelview.reset(); + modelview.translate(-centerX, -centerY); + + modelviewInv.set(modelview); + modelviewInv.invert(); + + camera.set(modelview); + cameraInv.set(modelviewInv); + + updateProjmodelview(); + } + + + @Override + protected void end2D() { + popMatrix(); + popProjection(); + } + + + ////////////////////////////////////////////////////////////// + + // SHAPE I/O + + + static protected boolean isSupportedExtension(String extension) { + return extension.equals("obj"); + } + + + static protected PShape loadShapeImpl(PGraphics pg, String filename, + String extension) { + PShapeOBJ obj = null; + + if (extension.equals("obj")) { + obj = new PShapeOBJ(pg.parent, filename); + + } else if (extension.equals("objz")) { + try { + // TODO: The obj file can be read from the gzip, but if it refers to + // a materials file and texture images, those must be contained in the + // data folder, cannot be inside the gzip. + InputStream input = + new GZIPInputStream(pg.parent.createInput(filename)); + obj = new PShapeOBJ(pg.parent, PApplet.createReader(input)); + } catch (Exception e) { + e.printStackTrace(); + } + } + + if (obj != null) { + int prevTextureMode = pg.textureMode; + pg.textureMode = NORMAL; + PShapeOpenGL p3d = PShapeOpenGL.createShape3D((PGraphicsOpenGL)pg, obj); + pg.textureMode = prevTextureMode; + return p3d; + } else { + return null; + } + } + + + ////////////////////////////////////////////////////////////// + + // SHAPE CREATION + + + @Override + public PShape createShape(PShape source) { + return PShapeOpenGL.createShape3D(this, source); + } + + + @Override + public PShape createShape() { + return createShape(PShape.GEOMETRY); + } + + + @Override + public PShape createShape(int type) { + return createShapeImpl(this, type); + } + + + @Override + public PShape createShape(int kind, float... p) { + return createShapeImpl(this, kind, p); + } + + + static protected PShapeOpenGL createShapeImpl(PGraphicsOpenGL pg, int type) { + PShapeOpenGL shape = null; + if (type == PConstants.GROUP) { + shape = new PShapeOpenGL(pg, PConstants.GROUP); + } else if (type == PShape.PATH) { + shape = new PShapeOpenGL(pg, PShape.PATH); + } else if (type == PShape.GEOMETRY) { + shape = new PShapeOpenGL(pg, PShape.GEOMETRY); + } + shape.set3D(true); + return shape; + } + + + static protected PShapeOpenGL createShapeImpl(PGraphicsOpenGL pg, + int kind, float... p) { + PShapeOpenGL shape = null; + int len = p.length; + + if (kind == POINT) { + if (len != 2 && len != 3) { + showWarning("Wrong number of parameters"); + return null; + } + shape = new PShapeOpenGL(pg, PShape.PRIMITIVE); + shape.setKind(POINT); + } else if (kind == LINE) { + if (len != 4 && len != 6) { + showWarning("Wrong number of parameters"); + return null; + } + shape = new PShapeOpenGL(pg, PShape.PRIMITIVE); + shape.setKind(LINE); + } else if (kind == TRIANGLE) { + if (len != 6) { + showWarning("Wrong number of parameters"); + return null; + } + shape = new PShapeOpenGL(pg, PShape.PRIMITIVE); + shape.setKind(TRIANGLE); + } else if (kind == QUAD) { + if (len != 8) { + showWarning("Wrong number of parameters"); + return null; + } + shape = new PShapeOpenGL(pg, PShape.PRIMITIVE); + shape.setKind(QUAD); + } else if (kind == RECT) { + if (len != 4 && len != 5 && len != 8 && len != 9) { + showWarning("Wrong number of parameters"); + return null; + } + shape = new PShapeOpenGL(pg, PShape.PRIMITIVE); + shape.setKind(RECT); + } else if (kind == ELLIPSE) { + if (len != 4 && len != 5) { + showWarning("Wrong number of parameters"); + return null; + } + shape = new PShapeOpenGL(pg, PShape.PRIMITIVE); + shape.setKind(ELLIPSE); + } else if (kind == ARC) { + if (len != 6 && len != 7) { + showWarning("Wrong number of parameters"); + return null; + } + shape = new PShapeOpenGL(pg, PShape.PRIMITIVE); + shape.setKind(ARC); + } else if (kind == BOX) { + if (len != 1 && len != 3) { + showWarning("Wrong number of parameters"); + return null; + } + shape = new PShapeOpenGL(pg, PShape.PRIMITIVE); + shape.setKind(BOX); + } else if (kind == SPHERE) { + if (len < 1 || 3 < len) { + showWarning("Wrong number of parameters"); + return null; + } + shape = new PShapeOpenGL(pg, PShape.PRIMITIVE); + shape.setKind(SPHERE); + } else { + showWarning("Unrecognized primitive type"); + } + + if (shape != null) { + shape.setParams(p); + } + + shape.set3D(true); + return shape; + } +} \ No newline at end of file diff --git a/java/libraries/jogl/src/processing/jogl/PGraphicsOpenGL.java b/java/libraries/jogl/src/processing/jogl/PGraphicsOpenGL.java new file mode 100644 index 000000000..447633e2c --- /dev/null +++ b/java/libraries/jogl/src/processing/jogl/PGraphicsOpenGL.java @@ -0,0 +1,12278 @@ +/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2004-13 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 version 2.1 as published by the Free Software Foundation. + + 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.opengl; + +import processing.core.*; + +import java.awt.Font; +import java.net.URL; +import java.nio.*; +import java.util.*; + +/** + * OpenGL renderer. + * + */ +public class PGraphicsOpenGL extends PGraphics { + /** Interface between Processing and OpenGL */ + public PGL pgl; + + /** The renderer currently in use. */ + protected PGraphicsOpenGL currentPG; + + /** Font cache for texture objects. */ + protected WeakHashMap fontMap; + + // ........................................................ + + // Basic rendering parameters: + + /** Whether the PGraphics object is ready to render or not. */ + public boolean initialized; + + /** Flush modes: continuously (geometry is flushed after each call to + * endShape) when-full (geometry is accumulated until a maximum size is + * reached. */ + static protected final int FLUSH_CONTINUOUSLY = 0; + static protected final int FLUSH_WHEN_FULL = 1; + + /** Type of geometry: immediate is that generated with beginShape/vertex/ + * endShape, retained is the result of creating a PShapeOpenGL object with + * createShape. */ + static protected final int IMMEDIATE = 0; + static protected final int RETAINED = 1; + + /** Current flush mode. */ + protected int flushMode = FLUSH_WHEN_FULL; + + // ........................................................ + + // VBOs for immediate rendering: + + public int glPolyVertex; + public int glPolyColor; + public int glPolyNormal; + public int glPolyTexcoord; + public int glPolyAmbient; + public int glPolySpecular; + public int glPolyEmissive; + public int glPolyShininess; + public int glPolyIndex; + protected boolean polyBuffersCreated = false; + protected int polyBuffersContext; + + public int glLineVertex; + public int glLineColor; + public int glLineAttrib; + public int glLineIndex; + protected boolean lineBuffersCreated = false; + protected int lineBuffersContext; + + public int glPointVertex; + public int glPointColor; + public int glPointAttrib; + public int glPointIndex; + protected boolean pointBuffersCreated = false; + protected int pointBuffersContext; + + static protected final int INIT_VERTEX_BUFFER_SIZE = 256; + static protected final int INIT_INDEX_BUFFER_SIZE = 512; + + // ........................................................ + + // GL parameters + + static protected boolean glParamsRead = false; + + /** Extensions used by Processing */ + static public boolean npotTexSupported; + static public boolean autoMipmapGenSupported; + static public boolean fboMultisampleSupported; + static public boolean packedDepthStencilSupported; + static public boolean anisoSamplingSupported; + static public boolean blendEqSupported; + + /** Some hardware limits */ + static public int maxTextureSize; + static public int maxSamples; + static public float maxAnisoAmount; + static public int depthBits; + static public int stencilBits; + + /** OpenGL information strings */ + static public String OPENGL_VENDOR; + static public String OPENGL_RENDERER; + static public String OPENGL_VERSION; + static public String OPENGL_EXTENSIONS; + static public String GLSL_VERSION; + + // ........................................................ + + // GL resources: + + static protected HashMap glTextureObjects = + new HashMap(); + static protected HashMap glVertexBuffers = + new HashMap(); + static protected HashMap glFrameBuffers = + new HashMap(); + static protected HashMap glRenderBuffers = + new HashMap(); + static protected HashMap glslPrograms = + new HashMap(); + static protected HashMap glslVertexShaders = + new HashMap(); + static protected HashMap glslFragmentShaders = + new HashMap(); + + // ........................................................ + + // Shaders + + static protected URL defColorShaderVertURL = + PGraphicsOpenGL.class.getResource("ColorVert.glsl"); + static protected URL defTextureShaderVertURL = + PGraphicsOpenGL.class.getResource("TextureVert.glsl"); + static protected URL defLightShaderVertURL = + PGraphicsOpenGL.class.getResource("LightVert.glsl"); + static protected URL defTexlightShaderVertURL = + PGraphicsOpenGL.class.getResource("TexlightVert.glsl"); + static protected URL defColorShaderFragURL = + PGraphicsOpenGL.class.getResource("ColorFrag.glsl"); + static protected URL defTextureShaderFragURL = + PGraphicsOpenGL.class.getResource("TextureFrag.glsl"); + static protected URL defLightShaderFragURL = + PGraphicsOpenGL.class.getResource("LightFrag.glsl"); + static protected URL defTexlightShaderFragURL = + PGraphicsOpenGL.class.getResource("TexlightFrag.glsl"); + + static protected URL defLineShaderVertURL = + PGraphicsOpenGL.class.getResource("LineVert.glsl"); + static protected URL defLineShaderFragURL = + PGraphicsOpenGL.class.getResource("LineFrag.glsl"); + static protected URL defPointShaderVertURL = + PGraphicsOpenGL.class.getResource("PointVert.glsl"); + static protected URL defPointShaderFragURL = + PGraphicsOpenGL.class.getResource("PointFrag.glsl"); + static protected URL maskShaderFragURL = + PGraphicsOpenGL.class.getResource("MaskFrag.glsl"); + + protected PShader defColorShader; + protected PShader defTextureShader; + protected PShader defLightShader; + protected PShader defTexlightShader; + protected PShader defLineShader; + protected PShader defPointShader; + protected PShader maskShader; + + protected PShader polyShader; + protected PShader lineShader; + protected PShader pointShader; + + // ........................................................ + + // Tessellator, geometry + + protected InGeometry inGeo; + protected TessGeometry tessGeo; + protected TexCache texCache; + static protected Tessellator tessellator; + + // ........................................................ + + // Camera: + + /** Camera field of view. */ + public float cameraFOV; + + /** Default position of the camera. */ + public float cameraX, cameraY, cameraZ; + /** Distance of the near and far planes. */ + public float cameraNear, cameraFar; + /** Aspect ratio of camera's view. */ + public float cameraAspect; + + /** Actual position of the camera. */ + protected float cameraEyeX, cameraEyeY, cameraEyeZ; + + /** Flag to indicate that we are inside beginCamera/endCamera block. */ + protected boolean manipulatingCamera; + + // ........................................................ + + // All the matrices required for camera and geometry transformations. + public PMatrix3D projection; + public PMatrix3D camera; + public PMatrix3D cameraInv; + public PMatrix3D modelview; + public PMatrix3D modelviewInv; + public PMatrix3D projmodelview; + + // To pass to shaders + protected float[] glProjection; + protected float[] glModelview; + protected float[] glProjmodelview; + protected float[] glNormal; + + // Useful to have around. + static protected PMatrix3D identity = new PMatrix3D(); + + /** + * Marks when changes to the size have occurred, so that the camera + * will be reset in beginDraw(). + */ + protected boolean sized; + + static protected final int MATRIX_STACK_DEPTH = 32; + + protected int modelviewStackDepth; + protected int projectionStackDepth; + + /** Modelview matrix stack **/ + protected float[][] modelviewStack = new float[MATRIX_STACK_DEPTH][16]; + + /** Inverse modelview matrix stack **/ + protected float[][] modelviewInvStack = new float[MATRIX_STACK_DEPTH][16]; + + /** Camera matrix stack **/ + protected float[][] cameraStack = new float[MATRIX_STACK_DEPTH][16]; + + /** Inverse camera matrix stack **/ + protected float[][] cameraInvStack = new float[MATRIX_STACK_DEPTH][16]; + + /** Projection matrix stack **/ + protected float[][] projectionStack = new float[MATRIX_STACK_DEPTH][16]; + + // ........................................................ + + // Lights: + + public boolean lights; + public int lightCount = 0; + + /** Light types */ + public int[] lightType; + + /** Light positions */ + public float[] lightPosition; + + /** Light direction (normalized vector) */ + public float[] lightNormal; + + /** + * Ambient colors for lights. + */ + public float[] lightAmbient; + + /** + * Diffuse colors for lights. + */ + public float[] lightDiffuse; + + /** + * Specular colors for lights. Internally these are stored as numbers between + * 0 and 1. + */ + public float[] lightSpecular; + + /** Light falloff */ + public float[] lightFalloffCoefficients; + + /** Light spot parameters: Cosine of light spot angle + * and concentration */ + public float[] lightSpotParameters; + + /** Current specular color for lighting */ + public float[] currentLightSpecular; + + /** Current light falloff */ + public float currentLightFalloffConstant; + public float currentLightFalloffLinear; + public float currentLightFalloffQuadratic; + + // ........................................................ + + // Texturing: + + protected int textureWrap = CLAMP; + protected int textureSampling = Texture.TRILINEAR; + + // ........................................................ + + // Clipping + + protected boolean clip = false; + + /** Clipping rectangle. */ + protected int[] clipRect = {0, 0, 0, 0}; + + + // ........................................................ + + // Text: + + /** Font texture of currently selected font. */ + FontTexture textTex; + + // ....................................................... + + // Framebuffer stack: + + static protected final int FB_STACK_DEPTH = 16; + + protected int fbStackDepth; + protected FrameBuffer[] fbStack; + protected FrameBuffer drawFramebuffer; + protected FrameBuffer readFramebuffer; + protected FrameBuffer currentFramebuffer; + + // ....................................................... + + // Offscreen rendering: + + protected FrameBuffer offscreenFramebuffer; + protected FrameBuffer multisampleFramebuffer; + protected boolean offscreenMultisample; + + protected boolean pixOpChangedFB; + + // ........................................................ + + // Screen surface: + + /** Texture containing the current frame */ + protected Texture texture; + + /** Texture containing the previous frame */ + protected Texture ptexture; + + /** IntBuffer wrapping the pixels array. */ + protected IntBuffer pixelBuffer; + + /** Array to store pixels in OpenGL format. */ + protected int[] nativePixels; + + /** IntBuffer wrapping the native pixels array. */ + protected IntBuffer nativePixelBuffer; + + /** texture used to apply a filter on the screen image. */ + protected Texture filterTexture; + + /** PImage that wraps filterTexture. */ + protected PImage filterImage; + + /** Flag to indicate that pixels array is up-to-date and + * ready to be manipulated through the set()/get() methods */ + protected boolean arePixelsUpToDate; + + // ........................................................ + + // Utility variables: + + /** True if we are inside a beginDraw()/endDraw() block. */ + protected boolean drawing = false; + + /** Used to indicate an OpenGL surface recreation */ + protected boolean restoreSurface = false; + + /** Used to detect continuous use of the smooth/noSmooth functions */ + protected boolean smoothDisabled = false; + protected int smoothCallCount = 0; + protected int lastSmoothCall = -10; + + /** Used to avoid flushing the geometry when blendMode() is called with the + * same blend mode as the last */ + protected int lastBlendMode = -1; + + /** Type of pixels operation. */ + static protected final int OP_NONE = 0; + static protected final int OP_READ = 1; + static protected final int OP_WRITE = 2; + protected int pixelsOp = OP_NONE; + + /** Viewport dimensions. */ + protected IntBuffer viewport; + + /** Used to register calls to glClear. */ + protected boolean clearColorBuffer; + protected boolean clearColorBuffer0; + + protected boolean openContour = false; + protected boolean breakShape = false; + protected boolean defaultEdges = false; + + static protected final int EDGE_MIDDLE = 0; + static protected final int EDGE_START = 1; + static protected final int EDGE_STOP = 2; + static protected final int EDGE_SINGLE = 3; + static protected final int EDGE_CLOSE = -1; + + /** Used in round point and ellipse tessellation. The + * number of subdivisions per round point or ellipse is + * calculated with the following formula: + * n = min(M, max(N, (TWO_PI * size / F))) + * where size is a measure of the dimensions of the circle + * when projected on screen coordinates. F just sets the + * minimum number of subdivisions, while a smaller F + * would allow to have more detailed circles. + * N = MIN_POINT_ACCURACY + * M = MAX_POINT_ACCURACY + * F = POINT_ACCURACY_FACTOR + */ + final static protected int MIN_POINT_ACCURACY = 20; + final static protected int MAX_POINT_ACCURACY = 200; + final static protected float POINT_ACCURACY_FACTOR = 10.0f; + + /** Used in quad point tessellation. */ + final static protected float[][] QUAD_POINT_SIGNS = + { {-1, +1}, {-1, -1}, {+1, -1}, {+1, +1} }; + + /** To get data from OpenGL. */ + static protected IntBuffer intBuffer; + static protected FloatBuffer floatBuffer; + + // ........................................................ + + // Error strings: + + static final String OPENGL_THREAD_ERROR = + "Cannot run the OpenGL renderer outside the main thread, change your code" + + "\nso the drawing calls are all inside the main thread, " + + "\nor use the default renderer instead."; + static final String BLEND_DRIVER_ERROR = + "blendMode(%1$s) is not supported by this hardware (or driver)"; + static final String BLEND_RENDERER_ERROR = + "blendMode(%1$s) is not supported by this renderer"; + static final String ALREADY_BEGAN_CONTOUR_ERROR = + "Already called beginContour()"; + static final String NO_BEGIN_CONTOUR_ERROR = + "Need to call beginContour() first"; + static final String UNSUPPORTED_SMOOTH_LEVEL_ERROR = + "Smooth level %1$s is not available. Using %2$s instead"; + static final String UNSUPPORTED_SMOOTH_ERROR = + "Smooth is not supported by this hardware (or driver)"; + static final String TOO_MANY_SMOOTH_CALLS_ERROR = + "The smooth/noSmooth functions are being called too often.\n" + + "This results in screen flickering, so they will be disabled\n" + + "for the rest of the sketch's execution"; + static final String UNSUPPORTED_SHAPE_FORMAT_ERROR = + "Unsupported shape format"; + static final String MISSING_UV_TEXCOORDS_ERROR = + "No uv texture coordinates supplied with vertex() call"; + static final String INVALID_FILTER_SHADER_ERROR = + "Your shader cannot be used as a filter because is of type POINT or LINES"; + static final String INCONSISTENT_SHADER_TYPES = + "The vertex and fragment shaders have different types"; + static final String WRONG_SHADER_TYPE_ERROR = + "shader() called with a wrong shader"; + static final String SHADER_NEED_LIGHT_ATTRIBS = + "The provided shader needs light attributes (ambient, diffuse, etc.), but " + + "the current scene is unlit, so the default shader will be used instead"; + static final String MISSING_FRAGMENT_SHADER = + "The fragment shader is missing, cannot create shader object"; + static final String MISSING_VERTEX_SHADER = + "The vertex shader is missing, cannot create shader object"; + static final String UNKNOWN_SHADER_KIND_ERROR = + "Unknown shader kind"; + static final String NO_TEXLIGHT_SHADER_ERROR = + "Your shader needs to be of TEXLIGHT type " + + "to render this geometry properly, using default shader instead."; + static final String NO_LIGHT_SHADER_ERROR = + "Your shader needs to be of LIGHT type " + + "to render this geometry properly, using default shader instead."; + static final String NO_TEXTURE_SHADER_ERROR = + "Your shader needs to be of TEXTURE type " + + "to render this geometry properly, using default shader instead."; + static final String NO_COLOR_SHADER_ERROR = + "Your shader needs to be of COLOR type " + + "to render this geometry properly, using default shader instead."; + static final String TOO_LONG_STROKE_PATH_ERROR = + "Stroke path is too long, some bevel triangles won't be added"; + static final String TESSELLATION_ERROR = + "Tessellation Error: %1$s"; + + ////////////////////////////////////////////////////////////// + + // INIT/ALLOCATE/FINISH + + + public PGraphicsOpenGL() { + pgl = createPGL(this); + + if (tessellator == null) { + tessellator = new Tessellator(); + } + + if (intBuffer == null) { + intBuffer = PGL.allocateIntBuffer(2); + floatBuffer = PGL.allocateFloatBuffer(2); + } + + viewport = PGL.allocateIntBuffer(4); + + inGeo = newInGeometry(this, IMMEDIATE); + tessGeo = newTessGeometry(this, IMMEDIATE); + texCache = newTexCache(this); + + projection = new PMatrix3D(); + camera = new PMatrix3D(); + cameraInv = new PMatrix3D(); + modelview = new PMatrix3D(); + modelviewInv = new PMatrix3D(); + projmodelview = new PMatrix3D(); + + lightType = new int[PGL.MAX_LIGHTS]; + lightPosition = new float[4 * PGL.MAX_LIGHTS]; + lightNormal = new float[3 * PGL.MAX_LIGHTS]; + lightAmbient = new float[3 * PGL.MAX_LIGHTS]; + lightDiffuse = new float[3 * PGL.MAX_LIGHTS]; + lightSpecular = new float[3 * PGL.MAX_LIGHTS]; + lightFalloffCoefficients = new float[3 * PGL.MAX_LIGHTS]; + lightSpotParameters = new float[2 * PGL.MAX_LIGHTS]; + currentLightSpecular = new float[3]; + + initialized = false; + } + + + @Override + public void setPrimary(boolean primary) { + super.setPrimary(primary); + pgl.setPrimary(primary); + format = ARGB; + if (primary) { + fbStack = new FrameBuffer[FB_STACK_DEPTH]; + fontMap = new WeakHashMap(); + } + } + + + //public void setPath(String path) // PGraphics + + + //public void setAntiAlias(int samples) // PGraphics + + +// @Override +// public void setFrameRate(float frameRate) { +// pgl.setFps(frameRate); +// } + + + @Override + public void setSize(int iwidth, int iheight) { + width = iwidth; + height = iheight; + + // init perspective projection based on new dimensions + cameraFOV = 60 * DEG_TO_RAD; // at least for now + cameraX = width / 2.0f; + cameraY = height / 2.0f; + cameraZ = cameraY / ((float) Math.tan(cameraFOV / 2.0f)); + cameraNear = cameraZ / 10.0f; + cameraFar = cameraZ * 10.0f; + cameraAspect = (float) width / (float) height; + + sized = true; + } + + + @Override + public void dispose() { // PGraphics + super.dispose(); + + if (primarySurface) { + // Swap buffers the end to make sure that no + // garbage is shown on the screen, this particularly + // affects non-interactive sketches on windows that + // render only 1 frame, so no enough rendering + // iterations have been conducted so far to properly + // initialize all the buffers. +// pgl.swapBuffers(); + } + + finalizePolyBuffers(); + finalizeLineBuffers(); + finalizePointBuffers(); + + deleteSurfaceTextures(); + if (primarySurface) { + deleteDefaultShaders(); + } else { + if (offscreenFramebuffer != null) { + offscreenFramebuffer.dispose(); + } + if (multisampleFramebuffer != null) { + multisampleFramebuffer.dispose(); + } + } + + deleteFinalizedGLResources(pgl); + + if (primarySurface) { + pgl.deleteSurface(); + } + } + + @Override + protected void finalize() throws Throwable { + try { + finalizePolyBuffers(); + finalizeLineBuffers(); + finalizePointBuffers(); + + deleteSurfaceTextures(); + if (!primarySurface) { + if (offscreenFramebuffer != null) { + offscreenFramebuffer.dispose(); + offscreenFramebuffer = null; + } + if (multisampleFramebuffer != null) { + multisampleFramebuffer.dispose(); + multisampleFramebuffer = null; + } + } + } finally { + super.finalize(); + } + } + + protected void setFlushMode(int mode) { + flushMode = mode; + } + + + @Override + public PSurface createSurface() { // ignore + return new PSurfaceNEWT(this); + } + + + ////////////////////////////////////////////////////////////// + + // IMAGE METADATA FOR THIS RENDERER + + + @Override + public void setCache(PImage image, Object storage) { + getPrimaryPG().cacheMap.put(image, storage); + } + + + @Override + public Object getCache(PImage image) { + return getPrimaryPG().cacheMap.get(image); + } + + + @Override + public void removeCache(PImage image) { + getPrimaryPG().cacheMap.remove(image); + } + + + ////////////////////////////////////////////////////////////// + + + protected void setFontTexture(PFont font, FontTexture fontTexture) { + getPrimaryPG().fontMap.put(font, fontTexture); + } + + + protected FontTexture getFontTexture(PFont font) { + return getPrimaryPG().fontMap.get(font); + } + + + protected void removeFontTexture(PFont font) { + getPrimaryPG().fontMap.remove(font); + } + + + ////////////////////////////////////////////////////////////// + + // RESOURCE HANDLING + + + protected static class GLResource { + int id; + int context; + + GLResource(int id, int context) { + this.id = id; + this.context = context; + } + + @Override + public boolean equals(Object obj) { + GLResource other = (GLResource)obj; + return other.id == id && other.context == context; + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + id; + result = 31 * result + context; + return result; + } + } + + + // Texture Objects ----------------------------------------------------------- + + protected static int createTextureObject(int context, PGL pgl) { + deleteFinalizedTextureObjects(pgl); + + pgl.genTextures(1, intBuffer); + int id = intBuffer.get(0); + + GLResource res = new GLResource(id, context); + if (!glTextureObjects.containsKey(res)) { + glTextureObjects.put(res, false); + } + + return id; + } + + protected static void deleteTextureObject(int id, int context, PGL pgl) { + GLResource res = new GLResource(id, context); + if (glTextureObjects.containsKey(res)) { + intBuffer.put(0, id); + if (pgl.threadIsCurrent()) pgl.deleteTextures(1, intBuffer); + glTextureObjects.remove(res); + } + } + + protected static void deleteAllTextureObjects(PGL pgl) { + for (GLResource res : glTextureObjects.keySet()) { + intBuffer.put(0, res.id); + if (pgl.threadIsCurrent()) pgl.deleteTextures(1, intBuffer); + } + glTextureObjects.clear(); + } + + // This is synchronized because it is called from the GC thread. + synchronized protected static void finalizeTextureObject(int id, int context) { + GLResource res = new GLResource(id, context); + if (glTextureObjects.containsKey(res)) { + glTextureObjects.put(res, true); + } + } + + protected static void deleteFinalizedTextureObjects(PGL pgl) { + Set finalized = new HashSet(); + + for (GLResource res : glTextureObjects.keySet()) { + if (glTextureObjects.get(res)) { + finalized.add(res); + intBuffer.put(0, res.id); + if (pgl.threadIsCurrent()) pgl.deleteTextures(1, intBuffer); + } + } + + for (GLResource res : finalized) { + glTextureObjects.remove(res); + } + } + + protected static void removeTextureObject(int id, int context) { + GLResource res = new GLResource(id, context); + if (glTextureObjects.containsKey(res)) { + glTextureObjects.remove(res); + } + } + + // Vertex Buffer Objects ----------------------------------------------------- + + protected static int createVertexBufferObject(int context, PGL pgl) { + deleteFinalizedVertexBufferObjects(pgl); + + pgl.genBuffers(1, intBuffer); + int id = intBuffer.get(0); + + GLResource res = new GLResource(id, context); + if (!glVertexBuffers.containsKey(res)) { + glVertexBuffers.put(res, false); + } + + return id; + } + + protected static void deleteVertexBufferObject(int id, int context, PGL pgl) { + GLResource res = new GLResource(id, context); + if (glVertexBuffers.containsKey(res)) { + intBuffer.put(0, id); + if (pgl.threadIsCurrent()) pgl.deleteBuffers(1, intBuffer); + glVertexBuffers.remove(res); + } + } + + protected static void deleteAllVertexBufferObjects(PGL pgl) { + for (GLResource res : glVertexBuffers.keySet()) { + intBuffer.put(0, res.id); + if (pgl.threadIsCurrent()) pgl.deleteBuffers(1, intBuffer); + } + glVertexBuffers.clear(); + } + + // This is synchronized because it is called from the GC thread. + synchronized static protected void finalizeVertexBufferObject(int id, int context) { + GLResource res = new GLResource(id, context); + if (glVertexBuffers.containsKey(res)) { + glVertexBuffers.put(res, true); + } + } + + protected static void deleteFinalizedVertexBufferObjects(PGL pgl) { + Set finalized = new HashSet(); + + for (GLResource res : glVertexBuffers.keySet()) { + if (glVertexBuffers.get(res)) { + finalized.add(res); + intBuffer.put(0, res.id); + if (pgl.threadIsCurrent()) pgl.deleteBuffers(1, intBuffer); + } + } + + for (GLResource res : finalized) { + glVertexBuffers.remove(res); + } + } + + protected static void removeVertexBufferObject(int id, int context) { + GLResource res = new GLResource(id, context); + if (glVertexBuffers.containsKey(res)) { + glVertexBuffers.remove(res); + } + } + + // FrameBuffer Objects ------------------------------------------------------- + + protected static int createFrameBufferObject(int context, PGL pgl) { + deleteFinalizedFrameBufferObjects(pgl); + + pgl.genFramebuffers(1, intBuffer); + int id = intBuffer.get(0); + + GLResource res = new GLResource(id, context); + if (!glFrameBuffers.containsKey(res)) { + glFrameBuffers.put(res, false); + } + + return id; + } + + protected static void deleteFrameBufferObject(int id, int context, PGL pgl) { + GLResource res = new GLResource(id, context); + if (glFrameBuffers.containsKey(res)) { + intBuffer.put(0, id); + if (pgl.threadIsCurrent()) pgl.deleteFramebuffers(1, intBuffer); + glFrameBuffers.remove(res); + } + } + + protected static void deleteAllFrameBufferObjects(PGL pgl) { + for (GLResource res : glFrameBuffers.keySet()) { + intBuffer.put(0, res.id); + if (pgl.threadIsCurrent()) pgl.deleteFramebuffers(1, intBuffer); + } + glFrameBuffers.clear(); + } + + // This is synchronized because it is called from the GC thread. + synchronized static protected void finalizeFrameBufferObject(int id, int context) { + GLResource res = new GLResource(id, context); + if (glFrameBuffers.containsKey(res)) { + glFrameBuffers.put(res, true); + } + } + + protected static void deleteFinalizedFrameBufferObjects(PGL pgl) { + Set finalized = new HashSet(); + + for (GLResource res : glFrameBuffers.keySet()) { + if (glFrameBuffers.get(res)) { + finalized.add(res); + intBuffer.put(0, res.id); + if (pgl.threadIsCurrent()) { + pgl.deleteFramebuffers(1, intBuffer); + } + } + } + + for (GLResource res : finalized) { + glFrameBuffers.remove(res); + } + } + + protected static void removeFrameBufferObject(int id, int context) { + GLResource res = new GLResource(id, context); + if (glFrameBuffers.containsKey(res)) { + glFrameBuffers.remove(res); + } + } + + // RenderBuffer Objects ------------------------------------------------------ + + protected static int createRenderBufferObject(int context, PGL pgl) { + deleteFinalizedRenderBufferObjects(pgl); + + pgl.genRenderbuffers(1, intBuffer); + int id = intBuffer.get(0); + + GLResource res = new GLResource(id, context); + if (!glRenderBuffers.containsKey(res)) { + glRenderBuffers.put(res, false); + } + + return id; + } + + protected static void deleteRenderBufferObject(int id, int context, PGL pgl) { + GLResource res = new GLResource(id, context); + if (glRenderBuffers.containsKey(res)) { + intBuffer.put(0, id); + if (pgl.threadIsCurrent()) pgl.deleteRenderbuffers(1, intBuffer); + glRenderBuffers.remove(res); + } + } + + protected static void deleteAllRenderBufferObjects(PGL pgl) { + for (GLResource res : glRenderBuffers.keySet()) { + intBuffer.put(0, res.id); + if (pgl.threadIsCurrent()) pgl.deleteRenderbuffers(1, intBuffer); + } + glRenderBuffers.clear(); + } + + // This is synchronized because it is called from the GC thread. + synchronized static protected void finalizeRenderBufferObject(int id, int context) { + GLResource res = new GLResource(id, context); + if (glRenderBuffers.containsKey(res)) { + glRenderBuffers.put(res, true); + } + } + + protected static void deleteFinalizedRenderBufferObjects(PGL pgl) { + Set finalized = new HashSet(); + + for (GLResource res : glRenderBuffers.keySet()) { + if (glRenderBuffers.get(res)) { + finalized.add(res); + intBuffer.put(0, res.id); + if (pgl.threadIsCurrent()) pgl.deleteRenderbuffers(1, intBuffer); + } + } + + for (GLResource res : finalized) { + glRenderBuffers.remove(res); + } + } + + protected static void removeRenderBufferObject(int id, int context) { + GLResource res = new GLResource(id, context); + if (glRenderBuffers.containsKey(res)) { + glRenderBuffers.remove(res); + } + } + + // GLSL Program Objects ------------------------------------------------------ + + protected static int createGLSLProgramObject(int context, PGL pgl) { + deleteFinalizedGLSLProgramObjects(pgl); + + int id = pgl.createProgram(); + + GLResource res = new GLResource(id, context); + if (!glslPrograms.containsKey(res)) { + glslPrograms.put(res, false); + } + + return id; + } + + protected static void deleteGLSLProgramObject(int id, int context, PGL pgl) { + GLResource res = new GLResource(id, context); + if (glslPrograms.containsKey(res)) { + if (pgl.threadIsCurrent()) pgl.deleteProgram(res.id); + glslPrograms.remove(res); + } + } + + protected static void deleteAllGLSLProgramObjects(PGL pgl) { + for (GLResource res : glslPrograms.keySet()) { + if (pgl.threadIsCurrent()) pgl.deleteProgram(res.id); + } + glslPrograms.clear(); + } + + // This is synchronized because it is called from the GC thread. + synchronized static protected void finalizeGLSLProgramObject(int id, int context) { + GLResource res = new GLResource(id, context); + if (glslPrograms.containsKey(res)) { + glslPrograms.put(res, true); + } + } + + protected static void deleteFinalizedGLSLProgramObjects(PGL pgl) { + Set finalized = new HashSet(); + + for (GLResource res : glslPrograms.keySet()) { + if (glslPrograms.get(res)) { + finalized.add(res); + if (pgl.threadIsCurrent()) pgl.deleteProgram(res.id); + } + } + + for (GLResource res : finalized) { + glslPrograms.remove(res); + } + } + + protected static void removeGLSLProgramObject(int id, int context) { + GLResource res = new GLResource(id, context); + if (glslPrograms.containsKey(res)) { + glslPrograms.remove(res); + } + } + + // GLSL Vertex Shader Objects ------------------------------------------------ + + protected static int createGLSLVertShaderObject(int context, PGL pgl) { + deleteFinalizedGLSLVertShaderObjects(pgl); + + int id = pgl.createShader(PGL.VERTEX_SHADER); + + GLResource res = new GLResource(id, context); + if (!glslVertexShaders.containsKey(res)) { + glslVertexShaders.put(res, false); + } + + return id; + } + + protected static void deleteGLSLVertShaderObject(int id, int context, PGL pgl) { + GLResource res = new GLResource(id, context); + if (glslVertexShaders.containsKey(res)) { + if (pgl.threadIsCurrent()) pgl.deleteShader(res.id); + glslVertexShaders.remove(res); + } + } + + protected static void deleteAllGLSLVertShaderObjects(PGL pgl) { + for (GLResource res : glslVertexShaders.keySet()) { + if (pgl.threadIsCurrent()) pgl.deleteShader(res.id); + } + glslVertexShaders.clear(); + } + + // This is synchronized because it is called from the GC thread. + synchronized static protected void finalizeGLSLVertShaderObject(int id, + int context) { + GLResource res = new GLResource(id, context); + if (glslVertexShaders.containsKey(res)) { + glslVertexShaders.put(res, true); + } + } + + protected static void deleteFinalizedGLSLVertShaderObjects(PGL pgl) { + Set finalized = new HashSet(); + + for (GLResource res : glslVertexShaders.keySet()) { + if (glslVertexShaders.get(res)) { + finalized.add(res); + if (pgl.threadIsCurrent()) pgl.deleteShader(res.id); + } + } + + for (GLResource res : finalized) { + glslVertexShaders.remove(res); + } + } + + protected static void removeGLSLVertShaderObject(int id, int context) { + GLResource res = new GLResource(id, context); + if (glslVertexShaders.containsKey(res)) { + glslVertexShaders.remove(res); + } + } + + // GLSL Fragment Shader Objects ---------------------------------------------- + + protected static int createGLSLFragShaderObject(int context, PGL pgl) { + deleteFinalizedGLSLFragShaderObjects(pgl); + + int id = pgl.createShader(PGL.FRAGMENT_SHADER); + + GLResource res = new GLResource(id, context); + if (!glslFragmentShaders.containsKey(res)) { + glslFragmentShaders.put(res, false); + } + + return id; + } + + protected static void deleteGLSLFragShaderObject(int id, int context, PGL pgl) { + GLResource res = new GLResource(id, context); + if (glslFragmentShaders.containsKey(res)) { + if (pgl.threadIsCurrent()) pgl.deleteShader(res.id); + glslFragmentShaders.remove(res); + } + } + + protected static void deleteAllGLSLFragShaderObjects(PGL pgl) { + for (GLResource res : glslFragmentShaders.keySet()) { + if (pgl.threadIsCurrent()) pgl.deleteShader(res.id); + } + glslFragmentShaders.clear(); + } + + // This is synchronized because it is called from the GC thread. + synchronized static protected void finalizeGLSLFragShaderObject(int id, + int context) { + GLResource res = new GLResource(id, context); + if (glslFragmentShaders.containsKey(res)) { + glslFragmentShaders.put(res, true); + } + } + + protected static void deleteFinalizedGLSLFragShaderObjects(PGL pgl) { + Set finalized = new HashSet(); + + for (GLResource res : glslFragmentShaders.keySet()) { + if (glslFragmentShaders.get(res)) { + finalized.add(res); + if (pgl.threadIsCurrent()) pgl.deleteShader(res.id); + } + } + + for (GLResource res : finalized) { + glslFragmentShaders.remove(res); + } + } + + protected static void removeGLSLFragShaderObject(int id, int context) { + GLResource res = new GLResource(id, context); + if (glslFragmentShaders.containsKey(res)) { + glslFragmentShaders.remove(res); + } + } + + // All OpenGL resources ------------------------------------------------------ + + protected static void deleteFinalizedGLResources(PGL pgl) { + deleteFinalizedTextureObjects(pgl); + deleteFinalizedVertexBufferObjects(pgl); + deleteFinalizedFrameBufferObjects(pgl); + deleteFinalizedRenderBufferObjects(pgl); + deleteFinalizedGLSLProgramObjects(pgl); + deleteFinalizedGLSLVertShaderObjects(pgl); + deleteFinalizedGLSLFragShaderObjects(pgl); + } + + + ////////////////////////////////////////////////////////////// + + // FRAMEBUFFERS + + + protected void pushFramebuffer() { + PGraphicsOpenGL ppg = getPrimaryPG(); + if (ppg.fbStackDepth == FB_STACK_DEPTH) { + throw new RuntimeException("Too many pushFramebuffer calls"); + } + ppg.fbStack[ppg.fbStackDepth] = ppg.currentFramebuffer; + ppg.fbStackDepth++; + } + + + protected void setFramebuffer(FrameBuffer fbo) { + PGraphicsOpenGL ppg = getPrimaryPG(); + if (ppg.currentFramebuffer != fbo) { + ppg.currentFramebuffer = fbo; + if (ppg.currentFramebuffer != null) ppg.currentFramebuffer.bind(); + } + } + + + protected void popFramebuffer() { + PGraphicsOpenGL ppg = getPrimaryPG(); + if (ppg.fbStackDepth == 0) { + throw new RuntimeException("popFramebuffer call is unbalanced."); + } + ppg.fbStackDepth--; + FrameBuffer fbo = ppg.fbStack[ppg.fbStackDepth]; + if (ppg.currentFramebuffer != fbo) { + ppg.currentFramebuffer.finish(); + ppg.currentFramebuffer = fbo; + if (ppg.currentFramebuffer != null) ppg.currentFramebuffer.bind(); + } + } + + + protected FrameBuffer getCurrentFB() { + return getPrimaryPG().currentFramebuffer; + } + + + ////////////////////////////////////////////////////////////// + + // FRAME RENDERING + + + protected void createPolyBuffers() { + if (!polyBuffersCreated || polyBuffersContextIsOutdated()) { + polyBuffersContext = pgl.getCurrentContext(); + + int sizef = INIT_VERTEX_BUFFER_SIZE * PGL.SIZEOF_FLOAT; + int sizei = INIT_VERTEX_BUFFER_SIZE * PGL.SIZEOF_INT; + int sizex = INIT_INDEX_BUFFER_SIZE * PGL.SIZEOF_INDEX; + + glPolyVertex = createVertexBufferObject(polyBuffersContext, pgl); + pgl.bindBuffer(PGL.ARRAY_BUFFER, glPolyVertex); + pgl.bufferData(PGL.ARRAY_BUFFER, 3 * sizef, null, PGL.STATIC_DRAW); + + glPolyColor = createVertexBufferObject(polyBuffersContext, pgl); + pgl.bindBuffer(PGL.ARRAY_BUFFER, glPolyColor); + pgl.bufferData(PGL.ARRAY_BUFFER, sizei, null, PGL.STATIC_DRAW); + + glPolyNormal = createVertexBufferObject(polyBuffersContext, pgl); + pgl.bindBuffer(PGL.ARRAY_BUFFER, glPolyNormal); + pgl.bufferData(PGL.ARRAY_BUFFER, 3 * sizef, null, PGL.STATIC_DRAW); + + glPolyTexcoord = createVertexBufferObject(polyBuffersContext, pgl); + pgl.bindBuffer(PGL.ARRAY_BUFFER, glPolyTexcoord); + pgl.bufferData(PGL.ARRAY_BUFFER, 2 * sizef, null, PGL.STATIC_DRAW); + + glPolyAmbient = createVertexBufferObject(polyBuffersContext, pgl); + pgl.bindBuffer(PGL.ARRAY_BUFFER, glPolyAmbient); + pgl.bufferData(PGL.ARRAY_BUFFER, sizei, null, PGL.STATIC_DRAW); + + glPolySpecular = createVertexBufferObject(polyBuffersContext, pgl); + pgl.bindBuffer(PGL.ARRAY_BUFFER, glPolySpecular); + pgl.bufferData(PGL.ARRAY_BUFFER, sizei, null, PGL.STATIC_DRAW); + + glPolyEmissive = createVertexBufferObject(polyBuffersContext, pgl); + pgl.bindBuffer(PGL.ARRAY_BUFFER, glPolyEmissive); + pgl.bufferData(PGL.ARRAY_BUFFER, sizei, null, PGL.STATIC_DRAW); + + glPolyShininess = createVertexBufferObject(polyBuffersContext, pgl); + pgl.bindBuffer(PGL.ARRAY_BUFFER, glPolyShininess); + pgl.bufferData(PGL.ARRAY_BUFFER, sizef, null, PGL.STATIC_DRAW); + + pgl.bindBuffer(PGL.ARRAY_BUFFER, 0); + + glPolyIndex = createVertexBufferObject(polyBuffersContext, pgl); + pgl.bindBuffer(PGL.ELEMENT_ARRAY_BUFFER, glPolyIndex); + pgl.bufferData(PGL.ELEMENT_ARRAY_BUFFER, sizex, null, PGL.STATIC_DRAW); + + pgl.bindBuffer(PGL.ELEMENT_ARRAY_BUFFER, 0); + + polyBuffersCreated = true; + } + } + + + protected void updatePolyBuffers(boolean lit, boolean tex, + boolean needNormals, boolean needTexCoords) { + createPolyBuffers(); + + int size = tessGeo.polyVertexCount; + int sizef = size * PGL.SIZEOF_FLOAT; + int sizei = size * PGL.SIZEOF_INT; + + tessGeo.updatePolyVerticesBuffer(); + pgl.bindBuffer(PGL.ARRAY_BUFFER, glPolyVertex); + pgl.bufferData(PGL.ARRAY_BUFFER, 4 * sizef, + tessGeo.polyVerticesBuffer, PGL.STATIC_DRAW); + + tessGeo.updatePolyColorsBuffer(); + pgl.bindBuffer(PGL.ARRAY_BUFFER, glPolyColor); + pgl.bufferData(PGL.ARRAY_BUFFER, sizei, + tessGeo.polyColorsBuffer, PGL.STATIC_DRAW); + + if (lit) { + tessGeo.updatePolyAmbientBuffer(); + pgl.bindBuffer(PGL.ARRAY_BUFFER, glPolyAmbient); + pgl.bufferData(PGL.ARRAY_BUFFER, sizei, + tessGeo.polyAmbientBuffer, PGL.STATIC_DRAW); + + tessGeo.updatePolySpecularBuffer(); + pgl.bindBuffer(PGL.ARRAY_BUFFER, glPolySpecular); + pgl.bufferData(PGL.ARRAY_BUFFER, sizei, + tessGeo.polySpecularBuffer, PGL.STATIC_DRAW); + + tessGeo.updatePolyEmissiveBuffer(); + pgl.bindBuffer(PGL.ARRAY_BUFFER, glPolyEmissive); + pgl.bufferData(PGL.ARRAY_BUFFER, sizei, + tessGeo.polyEmissiveBuffer, PGL.STATIC_DRAW); + + tessGeo.updatePolyShininessBuffer(); + pgl.bindBuffer(PGL.ARRAY_BUFFER, glPolyShininess); + pgl.bufferData(PGL.ARRAY_BUFFER, sizef, + tessGeo.polyShininessBuffer, PGL.STATIC_DRAW); + } + if (lit || needNormals) { + tessGeo.updatePolyNormalsBuffer(); + pgl.bindBuffer(PGL.ARRAY_BUFFER, glPolyNormal); + pgl.bufferData(PGL.ARRAY_BUFFER, 3 * sizef, + tessGeo.polyNormalsBuffer, PGL.STATIC_DRAW); + } + + if (tex || needTexCoords) { + tessGeo.updatePolyTexCoordsBuffer(); + pgl.bindBuffer(PGL.ARRAY_BUFFER, glPolyTexcoord); + pgl.bufferData(PGL.ARRAY_BUFFER, 2 * sizef, + tessGeo.polyTexCoordsBuffer, PGL.STATIC_DRAW); + } + + tessGeo.updatePolyIndicesBuffer(); + pgl.bindBuffer(PGL.ELEMENT_ARRAY_BUFFER, glPolyIndex); + pgl.bufferData(PGL.ELEMENT_ARRAY_BUFFER, + tessGeo.polyIndexCount * PGL.SIZEOF_INDEX, tessGeo.polyIndicesBuffer, + PGL.STATIC_DRAW); + } + + + protected void unbindPolyBuffers() { + pgl.bindBuffer(PGL.ARRAY_BUFFER, 0); + pgl.bindBuffer(PGL.ELEMENT_ARRAY_BUFFER, 0); + } + + + protected boolean polyBuffersContextIsOutdated() { + return !pgl.contextIsCurrent(polyBuffersContext); + } + + + protected void finalizePolyBuffers() { + if (glPolyVertex != 0) { + PGraphicsOpenGL.finalizeVertexBufferObject(glPolyVertex, polyBuffersContext); + glPolyVertex = 0; + } + + if (glPolyColor != 0) { + PGraphicsOpenGL.finalizeVertexBufferObject(glPolyColor, polyBuffersContext); + glPolyColor = 0; + } + + if (glPolyNormal != 0) { + PGraphicsOpenGL.finalizeVertexBufferObject(glPolyNormal, polyBuffersContext); + glPolyNormal = 0; + } + + if (glPolyTexcoord != 0) { + PGraphicsOpenGL.finalizeVertexBufferObject(glPolyTexcoord, polyBuffersContext); + glPolyTexcoord = 0; + } + + if (glPolyAmbient != 0) { + PGraphicsOpenGL.finalizeVertexBufferObject(glPolyAmbient, polyBuffersContext); + glPolyAmbient = 0; + } + + if (glPolySpecular != 0) { + PGraphicsOpenGL.finalizeVertexBufferObject(glPolySpecular, polyBuffersContext); + glPolySpecular = 0; + } + + if (glPolyEmissive != 0) { + PGraphicsOpenGL.finalizeVertexBufferObject(glPolyEmissive, polyBuffersContext); + glPolyEmissive = 0; + } + + if (glPolyShininess != 0) { + PGraphicsOpenGL.finalizeVertexBufferObject(glPolyShininess, polyBuffersContext); + glPolyShininess = 0; + } + + if (glPolyIndex != 0) { + PGraphicsOpenGL.finalizeVertexBufferObject(glPolyIndex, polyBuffersContext); + glPolyIndex = 0; + } + + polyBuffersCreated = false; + } + + + protected void createLineBuffers() { + if (!lineBuffersCreated || lineBufferContextIsOutdated()) { + lineBuffersContext = pgl.getCurrentContext(); + + int sizef = INIT_VERTEX_BUFFER_SIZE * PGL.SIZEOF_FLOAT; + int sizei = INIT_VERTEX_BUFFER_SIZE * PGL.SIZEOF_INT; + int sizex = INIT_INDEX_BUFFER_SIZE * PGL.SIZEOF_INDEX; + + glLineVertex = createVertexBufferObject(lineBuffersContext, pgl); + + pgl.bindBuffer(PGL.ARRAY_BUFFER, glLineVertex); + pgl.bufferData(PGL.ARRAY_BUFFER, 3 * sizef, null, PGL.STATIC_DRAW); + + glLineColor = createVertexBufferObject(lineBuffersContext, pgl); + pgl.bindBuffer(PGL.ARRAY_BUFFER, glLineColor); + pgl.bufferData(PGL.ARRAY_BUFFER, sizei, null, PGL.STATIC_DRAW); + + glLineAttrib = createVertexBufferObject(lineBuffersContext, pgl); + pgl.bindBuffer(PGL.ARRAY_BUFFER, glLineAttrib); + pgl.bufferData(PGL.ARRAY_BUFFER, 4 * sizef, null, PGL.STATIC_DRAW); + + pgl.bindBuffer(PGL.ARRAY_BUFFER, 0); + + glLineIndex = createVertexBufferObject(lineBuffersContext, pgl); + pgl.bindBuffer(PGL.ELEMENT_ARRAY_BUFFER, glLineIndex); + pgl.bufferData(PGL.ELEMENT_ARRAY_BUFFER, sizex, null, PGL.STATIC_DRAW); + + pgl.bindBuffer(PGL.ELEMENT_ARRAY_BUFFER, 0); + + lineBuffersCreated = true; + } + } + + + protected void updateLineBuffers() { + createLineBuffers(); + + int size = tessGeo.lineVertexCount; + int sizef = size * PGL.SIZEOF_FLOAT; + int sizei = size * PGL.SIZEOF_INT; + + tessGeo.updateLineVerticesBuffer(); + pgl.bindBuffer(PGL.ARRAY_BUFFER, glLineVertex); + pgl.bufferData(PGL.ARRAY_BUFFER, 4 * sizef, tessGeo.lineVerticesBuffer, + PGL.STATIC_DRAW); + + tessGeo.updateLineColorsBuffer(); + pgl.bindBuffer(PGL.ARRAY_BUFFER, glLineColor); + pgl.bufferData(PGL.ARRAY_BUFFER, sizei, + tessGeo.lineColorsBuffer, PGL.STATIC_DRAW); + + tessGeo.updateLineDirectionsBuffer(); + pgl.bindBuffer(PGL.ARRAY_BUFFER, glLineAttrib); + pgl.bufferData(PGL.ARRAY_BUFFER, 4 * sizef, + tessGeo.lineDirectionsBuffer, PGL.STATIC_DRAW); + + tessGeo.updateLineIndicesBuffer(); + pgl.bindBuffer(PGL.ELEMENT_ARRAY_BUFFER, glLineIndex); + pgl.bufferData(PGL.ELEMENT_ARRAY_BUFFER, + tessGeo.lineIndexCount * PGL.SIZEOF_INDEX, + tessGeo.lineIndicesBuffer, PGL.STATIC_DRAW); + } + + + protected void unbindLineBuffers() { + pgl.bindBuffer(PGL.ARRAY_BUFFER, 0); + pgl.bindBuffer(PGL.ELEMENT_ARRAY_BUFFER, 0); + } + + + protected boolean lineBufferContextIsOutdated() { + return !pgl.contextIsCurrent(lineBuffersContext); + } + + + protected void finalizeLineBuffers() { + if (glLineVertex != 0) { + PGraphicsOpenGL.finalizeVertexBufferObject(glLineVertex, lineBuffersContext); + glLineVertex = 0; + } + + if (glLineColor != 0) { + PGraphicsOpenGL.finalizeVertexBufferObject(glLineColor, lineBuffersContext); + glLineColor = 0; + } + + if (glLineAttrib != 0) { + PGraphicsOpenGL.finalizeVertexBufferObject(glLineAttrib, lineBuffersContext); + glLineAttrib = 0; + } + + if (glLineIndex != 0) { + PGraphicsOpenGL.finalizeVertexBufferObject(glLineIndex, lineBuffersContext); + glLineIndex = 0; + } + + lineBuffersCreated = false; + } + + + protected void createPointBuffers() { + if (!pointBuffersCreated || pointBuffersContextIsOutdated()) { + pointBuffersContext = pgl.getCurrentContext(); + + int sizef = INIT_VERTEX_BUFFER_SIZE * PGL.SIZEOF_FLOAT; + int sizei = INIT_VERTEX_BUFFER_SIZE * PGL.SIZEOF_INT; + int sizex = INIT_INDEX_BUFFER_SIZE * PGL.SIZEOF_INDEX; + + glPointVertex = createVertexBufferObject(pointBuffersContext, pgl); + pgl.bindBuffer(PGL.ARRAY_BUFFER, glPointVertex); + pgl.bufferData(PGL.ARRAY_BUFFER, 3 * sizef, null, PGL.STATIC_DRAW); + + glPointColor = createVertexBufferObject(pointBuffersContext, pgl); + pgl.bindBuffer(PGL.ARRAY_BUFFER, glPointColor); + pgl.bufferData(PGL.ARRAY_BUFFER, sizei, null, PGL.STATIC_DRAW); + + glPointAttrib = createVertexBufferObject(pointBuffersContext, pgl); + pgl.bindBuffer(PGL.ARRAY_BUFFER, glPointAttrib); + pgl.bufferData(PGL.ARRAY_BUFFER, 2 * sizef, null, PGL.STATIC_DRAW); + + pgl.bindBuffer(PGL.ARRAY_BUFFER, 0); + + glPointIndex = createVertexBufferObject(pointBuffersContext, pgl); + pgl.bindBuffer(PGL.ELEMENT_ARRAY_BUFFER, glPointIndex); + pgl.bufferData(PGL.ELEMENT_ARRAY_BUFFER, sizex, null, PGL.STATIC_DRAW); + + pgl.bindBuffer(PGL.ELEMENT_ARRAY_BUFFER, 0); + + pointBuffersCreated = true; + } + } + + + protected void updatePointBuffers() { + createPointBuffers(); + + int size = tessGeo.pointVertexCount; + int sizef = size * PGL.SIZEOF_FLOAT; + int sizei = size * PGL.SIZEOF_INT; + + tessGeo.updatePointVerticesBuffer(); + pgl.bindBuffer(PGL.ARRAY_BUFFER, glPointVertex); + pgl.bufferData(PGL.ARRAY_BUFFER, 4 * sizef, + tessGeo.pointVerticesBuffer, PGL.STATIC_DRAW); + + tessGeo.updatePointColorsBuffer(); + pgl.bindBuffer(PGL.ARRAY_BUFFER, glPointColor); + pgl.bufferData(PGL.ARRAY_BUFFER, sizei, + tessGeo.pointColorsBuffer, PGL.STATIC_DRAW); + + tessGeo.updatePointOffsetsBuffer(); + pgl.bindBuffer(PGL.ARRAY_BUFFER, glPointAttrib); + pgl.bufferData(PGL.ARRAY_BUFFER, 2 * sizef, + tessGeo.pointOffsetsBuffer, PGL.STATIC_DRAW); + + tessGeo.updatePointIndicesBuffer(); + pgl.bindBuffer(PGL.ELEMENT_ARRAY_BUFFER, glPointIndex); + pgl.bufferData(PGL.ELEMENT_ARRAY_BUFFER, + tessGeo.pointIndexCount * PGL.SIZEOF_INDEX, + tessGeo.pointIndicesBuffer, PGL.STATIC_DRAW); + } + + + protected void unbindPointBuffers() { + pgl.bindBuffer(PGL.ARRAY_BUFFER, 0); + pgl.bindBuffer(PGL.ELEMENT_ARRAY_BUFFER, 0); + } + + + protected boolean pointBuffersContextIsOutdated() { + return !pgl.contextIsCurrent(pointBuffersContext); + } + + + protected void finalizePointBuffers() { + if (glPointVertex != 0) { + PGraphicsOpenGL.finalizeVertexBufferObject(glPointVertex, pointBuffersContext); + glPointVertex = 0; + } + + if (glPointColor != 0) { + PGraphicsOpenGL.finalizeVertexBufferObject(glPointColor, pointBuffersContext); + glPointColor = 0; + } + + if (glPointAttrib != 0) { + PGraphicsOpenGL.finalizeVertexBufferObject(glPointAttrib, pointBuffersContext); + glPointAttrib = 0; + } + + if (glPointIndex != 0) { + PGraphicsOpenGL.finalizeVertexBufferObject(glPointIndex, pointBuffersContext); + glPointIndex = 0; + } + + pointBuffersCreated = false; + } + + +// @Override +// public void requestFocus() { +// pgl.requestFocus(); +// } + + + /** + * OpenGL cannot draw until a proper native peer is available, so this + * returns the value of PApplet.isDisplayable() (inherited from Component). + */ +// @Override +// public boolean canDraw() { +// return pgl.canDraw(); +// } + + +// @Override +// public void requestDraw() { +// if (primarySurface) { +// if (initialized) { +// if (sized) pgl.reinitSurface(); +// if (parent.canDraw()) pgl.requestDraw(); +// } else { +// initPrimary(); +// } +// } +// } + + + @Override + public void beginDraw() { + if (primarySurface) { +// if (initialized) { +// if (sized) pgl.reinitSurface(); +// if (parent.canDraw()) pgl.requestDraw(); +// } else { +// initPrimary(); +// } + + if (!initialized) { + initPrimary(); + } + + setCurrentPG(this); + } else { + pgl.getGL(getPrimaryPGL()); + getPrimaryPG().setCurrentPG(this); + } + + report("top beginDraw()"); + + if (!checkGLThread()) { + return; + } + + if (drawing) { + return; + } + + if (!primarySurface && getPrimaryPG().texCache.containsTexture(this)) { + // This offscreen surface is being used as a texture earlier in draw, + // so we should update the rendering up to this point since it will be + // modified. + getPrimaryPG().flush(); + } + + if (!glParamsRead) { + getGLParameters(); + } + + setViewport(); + if (primarySurface) { + beginOnscreenDraw(); + } else { + beginOffscreenDraw(); + } + setDrawDefaults(); // TODO: look at using checkSettings() instead... + + drawing = true; + + report("bot beginDraw()"); + } + + + @Override + public void endDraw() { + report("top endDraw()"); + + if (!drawing) { + return; + } + + // Flushing any remaining geometry. + flush(); + + if (PGL.SAVE_SURFACE_TO_PIXELS_HACK && + (!getPrimaryPG().initialized || parent.frameCount == 0)) { + // Smooth was disabled/enabled at some point during drawing. We save + // the current contents of the back buffer (because the buffers haven't + // been swapped yet) to the pixels array. The frameCount == 0 condition + // is to handle the situation when no smooth is called in setup in the + // PDE, but the OpenGL appears to be recreated due to the size() nastiness. + saveSurfaceToPixels(); + restoreSurface = true; + } + + if (primarySurface) { + endOnscreenDraw(); + } else { + endOffscreenDraw(); + } + + if (primarySurface) { + setCurrentPG(null); + } else { + getPrimaryPG().setCurrentPG(getPrimaryPG()); + } + drawing = false; + + report("bot endDraw()"); + } + + + // Factory method + protected PGL createPGL(PGraphicsOpenGL pg) { + return new PJOGL(pg); + } + + + protected PGraphicsOpenGL getPrimaryPG() { + if (primarySurface) { + return this; + } else { + return (PGraphicsOpenGL)parent.g; + } + } + + protected void setCurrentPG(PGraphicsOpenGL pg) { + currentPG = pg; + } + + protected PGraphicsOpenGL getCurrentPG() { + return currentPG; + } + + protected PGL getPrimaryPGL() { + if (primarySurface) { + return pgl; + } else { + return ((PGraphicsOpenGL)parent.g).pgl; + } + } + + + @Override + public PGL beginPGL() { + flush(); + pgl.beginGL(); + return pgl; + } + + + @Override + public void endPGL() { + pgl.endGL(); + restoreGL(); + } + + + public void updateProjmodelview() { + projmodelview.set(projection); + projmodelview.apply(modelview); + } + + + protected void restartPGL() { + initialized = false; + } + + + protected void restoreGL() { + blendMode(blendMode); // this should be set by reapplySettings... + + if (hints[DISABLE_DEPTH_TEST]) { + pgl.disable(PGL.DEPTH_TEST); + } else { + pgl.enable(PGL.DEPTH_TEST); + } + pgl.depthFunc(PGL.LEQUAL); + + if (quality < 2) { + pgl.disable(PGL.MULTISAMPLE); + } else { + pgl.enable(PGL.MULTISAMPLE); + pgl.disable(PGL.POLYGON_SMOOTH); + } + + pgl.viewport(viewport.get(0), viewport.get(1), + viewport.get(2), viewport.get(3)); + if (clip) { + pgl.enable(PGL.SCISSOR_TEST); + pgl.scissor(clipRect[0], clipRect[1], clipRect[2], clipRect[3]); + } else { + pgl.disable(PGL.SCISSOR_TEST); + } + + pgl.frontFace(PGL.CW); + pgl.disable(PGL.CULL_FACE); + + pgl.activeTexture(PGL.TEXTURE0); + + if (hints[DISABLE_DEPTH_MASK]) { + pgl.depthMask(false); + } else { + pgl.depthMask(true); + } + + FrameBuffer fb = getCurrentFB(); + if (fb != null) { + fb.bind(); + pgl.drawBuffer(fb.getDefaultDrawBuffer()); + } + } + + protected void beginBindFramebuffer(int target, int framebuffer) { + // Actually, nothing to do here. + } + + protected void endBindFramebuffer(int target, int framebuffer) { + FrameBuffer fb = getCurrentFB(); + if (framebuffer == 0 && fb != null && fb.glFbo != 0) { + // The user is setting the framebuffer to 0 (screen buffer), but the + // renderer is drawing into an offscreen buffer. + fb.bind(); + } + } + + protected void beginReadPixels() { + beginPixelsOp(OP_READ); + } + + protected void endReadPixels() { + endPixelsOp(); + } + + protected void beginPixelsOp(int op) { + FrameBuffer pixfb = null; + if (primarySurface) { + if (op == OP_READ) { + if (pgl.isFBOBacked() && pgl.isMultisampled()) { + // Making sure the back texture is up-to-date... + pgl.syncBackTexture(); + // ...because the read framebuffer uses it as the color buffer (the + // draw framebuffer is MSAA so it cannot be read from it). + pixfb = readFramebuffer; + } else { + pixfb = drawFramebuffer; + } + } else if (op == OP_WRITE) { + // We can write to the draw framebuffer irrespective of whether is + // FBO-baked or multisampled. + pixfb = drawFramebuffer; + } + } else { + if (op == OP_READ) { + if (offscreenMultisample) { + // Making sure the offscreen FBO is up-to-date + multisampleFramebuffer.copyColor(offscreenFramebuffer); + } + // We always read the screen pixels from the color FBO. + pixfb = offscreenFramebuffer; + } else if (op == OP_WRITE) { + // We can write directly to the color FBO, or to the multisample FBO + // if multisampling is enabled. + pixfb = offscreenMultisample ? multisampleFramebuffer : + offscreenFramebuffer; + } + } + + // Set the framebuffer where the pixel operation shall be carried out. + if (pixfb != getCurrentFB()) { + pushFramebuffer(); + setFramebuffer(pixfb); + pixOpChangedFB = true; + } + + // We read from/write to the draw buffer. + if (op == OP_READ) { + pgl.readBuffer(getCurrentFB().getDefaultDrawBuffer()); + } else if (op == OP_WRITE) { + pgl.drawBuffer(getCurrentFB().getDefaultDrawBuffer()); + } + + pixelsOp = op; + } + + + protected void endPixelsOp() { + // Restoring current framebuffer prior to the pixel operation + if (pixOpChangedFB) { + popFramebuffer(); + pixOpChangedFB = false; + } + + // Restoring default read/draw buffer configuration. + pgl.readBuffer(getCurrentFB().getDefaultReadBuffer()); + pgl.drawBuffer(getCurrentFB().getDefaultDrawBuffer()); + + pixelsOp = OP_NONE; + } + + + protected void updateGLProjection() { + if (glProjection == null) { + glProjection = new float[16]; + } + + glProjection[0] = projection.m00; + glProjection[1] = projection.m10; + glProjection[2] = projection.m20; + glProjection[3] = projection.m30; + + glProjection[4] = projection.m01; + glProjection[5] = projection.m11; + glProjection[6] = projection.m21; + glProjection[7] = projection.m31; + + glProjection[8] = projection.m02; + glProjection[9] = projection.m12; + glProjection[10] = projection.m22; + glProjection[11] = projection.m32; + + glProjection[12] = projection.m03; + glProjection[13] = projection.m13; + glProjection[14] = projection.m23; + glProjection[15] = projection.m33; + } + + + protected void updateGLModelview() { + if (glModelview == null) { + glModelview = new float[16]; + } + + glModelview[0] = modelview.m00; + glModelview[1] = modelview.m10; + glModelview[2] = modelview.m20; + glModelview[3] = modelview.m30; + + glModelview[4] = modelview.m01; + glModelview[5] = modelview.m11; + glModelview[6] = modelview.m21; + glModelview[7] = modelview.m31; + + glModelview[8] = modelview.m02; + glModelview[9] = modelview.m12; + glModelview[10] = modelview.m22; + glModelview[11] = modelview.m32; + + glModelview[12] = modelview.m03; + glModelview[13] = modelview.m13; + glModelview[14] = modelview.m23; + glModelview[15] = modelview.m33; + } + + + protected void updateGLProjmodelview() { + if (glProjmodelview == null) { + glProjmodelview = new float[16]; + } + + glProjmodelview[0] = projmodelview.m00; + glProjmodelview[1] = projmodelview.m10; + glProjmodelview[2] = projmodelview.m20; + glProjmodelview[3] = projmodelview.m30; + + glProjmodelview[4] = projmodelview.m01; + glProjmodelview[5] = projmodelview.m11; + glProjmodelview[6] = projmodelview.m21; + glProjmodelview[7] = projmodelview.m31; + + glProjmodelview[8] = projmodelview.m02; + glProjmodelview[9] = projmodelview.m12; + glProjmodelview[10] = projmodelview.m22; + glProjmodelview[11] = projmodelview.m32; + + glProjmodelview[12] = projmodelview.m03; + glProjmodelview[13] = projmodelview.m13; + glProjmodelview[14] = projmodelview.m23; + glProjmodelview[15] = projmodelview.m33; + } + + + protected void updateGLNormal() { + if (glNormal == null) { + glNormal = new float[9]; + } + + // The normal matrix is the transpose of the inverse of the + // modelview (remember that gl matrices are column-major, + // meaning that elements 0, 1, 2 are the first column, + // 3, 4, 5 the second, etc.): + glNormal[0] = modelviewInv.m00; + glNormal[1] = modelviewInv.m01; + glNormal[2] = modelviewInv.m02; + + glNormal[3] = modelviewInv.m10; + glNormal[4] = modelviewInv.m11; + glNormal[5] = modelviewInv.m12; + + glNormal[6] = modelviewInv.m20; + glNormal[7] = modelviewInv.m21; + glNormal[8] = modelviewInv.m22; + } + + + ////////////////////////////////////////////////////////////// + + // SETTINGS + + // protected void checkSettings() + + + @Override + protected void defaultSettings() { + super.defaultSettings(); + + manipulatingCamera = false; + + clearColorBuffer = false; + + // easiest for beginners + textureMode(IMAGE); + + // Default material properties + ambient(255); + specular(125); + emissive(0); + shininess(1); + + // To indicate that the user hasn't set ambient + setAmbient = false; + } + + + // reapplySettings + + ////////////////////////////////////////////////////////////// + + // HINTS + + + @Override + public void hint(int which) { + boolean oldValue = hints[PApplet.abs(which)]; + super.hint(which); + boolean newValue = hints[PApplet.abs(which)]; + + if (oldValue == newValue) { + return; + } + + if (which == DISABLE_DEPTH_TEST) { + flush(); + pgl.disable(PGL.DEPTH_TEST); + } else if (which == ENABLE_DEPTH_TEST) { + flush(); + pgl.enable(PGL.DEPTH_TEST); + } else if (which == DISABLE_DEPTH_MASK) { + flush(); + pgl.depthMask(false); + } else if (which == ENABLE_DEPTH_MASK) { + flush(); + pgl.depthMask(true); + } else if (which == ENABLE_OPTIMIZED_STROKE) { + flush(); + setFlushMode(FLUSH_WHEN_FULL); + } else if (which == DISABLE_OPTIMIZED_STROKE) { + if (is2D()) { + PGraphics.showWarning("Optimized strokes can only be disabled in 3D"); + } else { + flush(); + setFlushMode(FLUSH_CONTINUOUSLY); + } + } else if (which == DISABLE_STROKE_PERSPECTIVE) { + if (0 < tessGeo.lineVertexCount && 0 < tessGeo.lineIndexCount) { + // We flush the geometry using the previous line setting. + flush(); + } + } else if (which == ENABLE_STROKE_PERSPECTIVE) { + if (0 < tessGeo.lineVertexCount && 0 < tessGeo.lineIndexCount) { + // We flush the geometry using the previous line setting. + flush(); + } + } + } + + + protected boolean getHint(int which) { + if (which > 0) { + return hints[which]; + } else { + return !hints[-which]; + } + } + + + ////////////////////////////////////////////////////////////// + + // VERTEX SHAPES + + + @Override + public void beginShape(int kind) { + shape = kind; + inGeo.clear(); + + curveVertexCount = 0; + breakShape = false; + defaultEdges = true; + + // The superclass method is called to avoid an early flush. + super.noTexture(); + + normalMode = NORMAL_MODE_AUTO; + } + + + @Override + public void endShape(int mode) { + tessellate(mode); + + if ((flushMode == FLUSH_CONTINUOUSLY) || + (flushMode == FLUSH_WHEN_FULL && tessGeo.isFull())) { + flush(); + } else { + // pixels array is not up-to-date anymore + arePixelsUpToDate = false; + } + } + + + protected void endShape(int[] indices) { + if (shape != TRIANGLE && shape != TRIANGLES) { + throw new RuntimeException("Indices and edges can only be set for " + + "TRIANGLE shapes"); + } + + tessellate(indices); + + if (flushMode == FLUSH_CONTINUOUSLY || + (flushMode == FLUSH_WHEN_FULL && tessGeo.isFull())) { + flush(); + } else { + // pixels array is not up-to-date anymore + arePixelsUpToDate = false; + } + } + + + @Override + public void textureWrap(int wrap) { + this.textureWrap = wrap; + } + + + public void textureSampling(int sampling) { + this.textureSampling = sampling; + } + + + @Override + public void beginContour() { + if (openContour) { + PGraphics.showWarning(ALREADY_BEGAN_CONTOUR_ERROR); + return; + } + openContour = true; + breakShape = true; + } + + + @Override + public void endContour() { + if (!openContour) { + PGraphics.showWarning(NO_BEGIN_CONTOUR_ERROR); + return; + } + openContour = false; + } + + + @Override + public void vertex(float x, float y) { + vertexImpl(x, y, 0, 0, 0); + if (textureImage != null) PGraphics.showWarning(MISSING_UV_TEXCOORDS_ERROR); + } + + + @Override + public void vertex(float x, float y, float u, float v) { + vertexImpl(x, y, 0, u, v); + } + + + @Override + public void vertex(float x, float y, float z) { + vertexImpl(x, y, z, 0, 0); + if (textureImage != null) PGraphics.showWarning(MISSING_UV_TEXCOORDS_ERROR); + } + + + @Override + public void vertex(float x, float y, float z, float u, float v) { + vertexImpl(x, y, z, u, v); + } + + + protected void vertexImpl(float x, float y, float z, float u, float v) { + boolean textured = textureImage != null; + int fcolor = 0x00; + if (fill || textured) { + if (!textured) { + fcolor = fillColor; + } else { + if (tint) { + fcolor = tintColor; + } else { + fcolor = 0xffFFFFFF; + } + } + } + + int scolor = 0x00; + float sweight = 0; + if (stroke) { + scolor = strokeColor; + sweight = strokeWeight; + } + + if (textured && textureMode == IMAGE) { + u /= textureImage.width; + v /= textureImage.height; + } + + inGeo.addVertex(x, y, z, + fcolor, + normalX, normalY, normalZ, + u, v, + scolor, sweight, + ambientColor, specularColor, emissiveColor, shininess, + VERTEX, vertexBreak()); + } + + + protected boolean vertexBreak() { + if (breakShape) { + breakShape = false; + return true; + } + return false; + } + + + @Override + protected void clipImpl(float x1, float y1, float x2, float y2) { + flush(); + pgl.enable(PGL.SCISSOR_TEST); + + float h = y2 - y1; + clipRect[0] = (int)x1; + clipRect[1] = (int)(height - y1 - h); + clipRect[2] = (int)(x2 - x1); + clipRect[3] = (int)h; + pgl.scissor(clipRect[0], clipRect[1], clipRect[2], clipRect[3]); + + clip = true; + } + + + @Override + public void noClip() { + if (clip) { + flush(); + pgl.disable(PGL.SCISSOR_TEST); + clip = false; + } + } + + + ////////////////////////////////////////////////////////////// + + // RENDERING + + // protected void render() + + // protected void sort() + + + protected void tessellate(int mode) { + tessellator.setInGeometry(inGeo); + tessellator.setTessGeometry(tessGeo); + tessellator.setFill(fill || textureImage != null); + tessellator.setTexCache(texCache, textureImage); + tessellator.setStroke(stroke); + tessellator.setStrokeColor(strokeColor); + tessellator.setStrokeWeight(strokeWeight); + tessellator.setStrokeCap(strokeCap); + tessellator.setStrokeJoin(strokeJoin); + tessellator.setRenderer(this); + tessellator.setTransform(modelview); + tessellator.set3D(is3D()); + + if (shape == POINTS) { + tessellator.tessellatePoints(); + } else if (shape == LINES) { + tessellator.tessellateLines(); + } else if (shape == LINE_STRIP) { + tessellator.tessellateLineStrip(); + } else if (shape == LINE_LOOP) { + tessellator.tessellateLineLoop(); + } else if (shape == TRIANGLE || shape == TRIANGLES) { + if (stroke && defaultEdges) inGeo.addTrianglesEdges(); + if (normalMode == NORMAL_MODE_AUTO) inGeo.calcTrianglesNormals(); + tessellator.tessellateTriangles(); + } else if (shape == TRIANGLE_FAN) { + if (stroke && defaultEdges) inGeo.addTriangleFanEdges(); + if (normalMode == NORMAL_MODE_AUTO) inGeo.calcTriangleFanNormals(); + tessellator.tessellateTriangleFan(); + } else if (shape == TRIANGLE_STRIP) { + if (stroke && defaultEdges) inGeo.addTriangleStripEdges(); + if (normalMode == NORMAL_MODE_AUTO) inGeo.calcTriangleStripNormals(); + tessellator.tessellateTriangleStrip(); + } else if (shape == QUAD || shape == QUADS) { + if (stroke && defaultEdges) inGeo.addQuadsEdges(); + if (normalMode == NORMAL_MODE_AUTO) inGeo.calcQuadsNormals(); + tessellator.tessellateQuads(); + } else if (shape == QUAD_STRIP) { + if (stroke && defaultEdges) inGeo.addQuadStripEdges(); + if (normalMode == NORMAL_MODE_AUTO) inGeo.calcQuadStripNormals(); + tessellator.tessellateQuadStrip(); + } else if (shape == POLYGON) { + tessellator.tessellatePolygon(true, mode == CLOSE, + normalMode == NORMAL_MODE_AUTO); + } + } + + + protected void tessellate(int[] indices) { + tessellator.setInGeometry(inGeo); + tessellator.setTessGeometry(tessGeo); + tessellator.setFill(fill || textureImage != null); + tessellator.setStroke(stroke); + tessellator.setStrokeColor(strokeColor); + tessellator.setStrokeWeight(strokeWeight); + tessellator.setStrokeCap(strokeCap); + tessellator.setStrokeJoin(strokeJoin); + tessellator.setTexCache(texCache, textureImage); + tessellator.setTransform(modelview); + tessellator.set3D(is3D()); + + if (stroke && defaultEdges) inGeo.addTrianglesEdges(); + if (normalMode == NORMAL_MODE_AUTO) inGeo.calcTrianglesNormals(); + tessellator.tessellateTriangles(indices); + } + + + @Override + public void flush() { + boolean hasPolys = 0 < tessGeo.polyVertexCount && + 0 < tessGeo.polyIndexCount; + boolean hasLines = 0 < tessGeo.lineVertexCount && + 0 < tessGeo.lineIndexCount; + boolean hasPoints = 0 < tessGeo.pointVertexCount && + 0 < tessGeo.pointIndexCount; + + boolean hasPixels = modified && pixels != null; + + if (hasPixels) { + // If the user has been manipulating individual pixels, + // the changes need to be copied to the screen before + // drawing any new geometry. + flushPixels(); + } + + if (hasPoints || hasLines || hasPolys) { + PMatrix3D modelview0 = null; + PMatrix3D modelviewInv0 = null; + if (flushMode == FLUSH_WHEN_FULL) { + // The modelview transformation has been applied already to the + // tessellated vertices, so we set the OpenGL modelview matrix as + // the identity to avoid applying the model transformations twice. + // We save the modelview objects and temporarily use the identity + // static matrix to avoid calling pushMatrix(), resetMatrix(), + // popMatrix(). + modelview0 = modelview; + modelviewInv0 = modelviewInv; + modelview = modelviewInv = identity; + projmodelview.set(projection); + } + + if (hasPolys) { + flushPolys(); + if (raw != null) { + rawPolys(); + } + } + + if (is3D()) { + if (hasLines) { + flushLines(); + if (raw != null) { + rawLines(); + } + } + + if (hasPoints) { + flushPoints(); + if (raw != null) { + rawPoints(); + } + } + } + + if (flushMode == FLUSH_WHEN_FULL) { + modelview = modelview0; + modelviewInv = modelviewInv0; + updateProjmodelview(); + } + } + + tessGeo.clear(); + texCache.clear(); + arePixelsUpToDate = false; + } + + + protected void flushPixels() { + drawPixels(mx1, my1, mx2 - mx1, my2 - my1); + modified = false; + } + + + protected void flushPolys() { + boolean customShader = polyShader != null; + boolean needNormals = customShader ? polyShader.accessNormals() : false; + boolean needTexCoords = customShader ? polyShader.accessTexCoords() : false; + + updatePolyBuffers(lights, texCache.hasTextures, needNormals, needTexCoords); + + for (int i = 0; i < texCache.size; i++) { + Texture tex = texCache.getTexture(i); + + // If the renderer is 2D, then lights should always be false, + // so no need to worry about that. + PShader shader = getPolyShader(lights, tex != null); + shader.bind(); + + int first = texCache.firstCache[i]; + int last = texCache.lastCache[i]; + IndexCache cache = tessGeo.polyIndexCache; + + for (int n = first; n <= last; n++) { + int ioffset = n == first ? texCache.firstIndex[i] : cache.indexOffset[n]; + int icount = n == last ? texCache.lastIndex[i] - ioffset + 1 : + cache.indexOffset[n] + cache.indexCount[n] - ioffset; + int voffset = cache.vertexOffset[n]; + + shader.setVertexAttribute(glPolyVertex, 4, PGL.FLOAT, 0, + 4 * voffset * PGL.SIZEOF_FLOAT); + shader.setColorAttribute(glPolyColor, 4, PGL.UNSIGNED_BYTE, 0, + 4 * voffset * PGL.SIZEOF_BYTE); + + if (lights) { + shader.setNormalAttribute(glPolyNormal, 3, PGL.FLOAT, 0, + 3 * voffset * PGL.SIZEOF_FLOAT); + shader.setAmbientAttribute(glPolyAmbient, 4, PGL.UNSIGNED_BYTE, 0, + 4 * voffset * PGL.SIZEOF_BYTE); + shader.setSpecularAttribute(glPolySpecular, 4, PGL.UNSIGNED_BYTE, 0, + 4 * voffset * PGL.SIZEOF_BYTE); + shader.setEmissiveAttribute(glPolyEmissive, 4, PGL.UNSIGNED_BYTE, 0, + 4 * voffset * PGL.SIZEOF_BYTE); + shader.setShininessAttribute(glPolyShininess, 1, PGL.FLOAT, 0, + voffset * PGL.SIZEOF_FLOAT); + } + + if (lights || needNormals) { + shader.setNormalAttribute(glPolyNormal, 3, PGL.FLOAT, 0, + 3 * voffset * PGL.SIZEOF_FLOAT); + } + + if (tex != null || needTexCoords) { + shader.setTexcoordAttribute(glPolyTexcoord, 2, PGL.FLOAT, 0, + 2 * voffset * PGL.SIZEOF_FLOAT); + shader.setTexture(tex); + } + + shader.draw(glPolyIndex, icount, ioffset); + } + + shader.unbind(); + } + unbindPolyBuffers(); + } + + + static class Triangle { + int i0, i1, i2; + PImage tex; + float dist; + Triangle(int i0, int i1, int i2, PImage tex, float dist) { + this.i0 = i0; + this.i1 = i1; + this.i2 = i2; + this.tex = tex; + this.dist = dist; + } + } + // Adapted from Ben Van Citters code: + // http://openprocessing.org/sketch/100912 + Triangle[] sortedPolyTriangles = null; + int sortedTriangleCount = 0; + void sortTriangles() { + if (sortedPolyTriangles == null) { + sortedPolyTriangles = new Triangle[512]; + } + + float[] vertices = tessGeo.polyVertices; + short[] indices = tessGeo.polyIndices; + float[] src0 = {0, 0, 0, 0}; + float[] src1 = {0, 0, 0, 0}; + float[] src2 = {0, 0, 0, 0}; + float[] pt0 = {0, 0, 0, 0}; + float[] pt1 = {0, 0, 0, 0}; + float[] pt2 = {0, 0, 0, 0}; + + sortedTriangleCount = 0; + for (int i = 0; i < texCache.size; i++) { + PImage textureImage = texCache.getTextureImage(i); + int first = texCache.firstCache[i]; + int last = texCache.lastCache[i]; + IndexCache cache = tessGeo.polyIndexCache; + for (int n = first; n <= last; n++) { + int ioffset = n == first ? texCache.firstIndex[i] : + cache.indexOffset[n]; + int icount = n == last ? texCache.lastIndex[i] - ioffset + 1 : + cache.indexOffset[n] + cache.indexCount[n] - + ioffset; + int voffset = cache.vertexOffset[n]; + for (int tr = ioffset / 3; tr < (ioffset + icount) / 3; tr++) { + if (sortedPolyTriangles.length == sortedTriangleCount) { + // expand array + int newSize = sortedTriangleCount << 1; + Triangle[] temp = new Triangle[newSize]; + PApplet.arrayCopy(sortedPolyTriangles, 0, temp, 0, newSize); + sortedPolyTriangles = temp; + } + + int i0 = voffset + indices[3 * tr + 0]; + int i1 = voffset + indices[3 * tr + 1]; + int i2 = voffset + indices[3 * tr + 2]; + PApplet.arrayCopy(vertices, 4 * i0, src0, 0, 4); + PApplet.arrayCopy(vertices, 4 * i1, src1, 0, 4); + PApplet.arrayCopy(vertices, 4 * i2, src2, 0, 4); + modelview.mult(src0, pt0); + modelview.mult(src1, pt1); + modelview.mult(src2, pt2); + // add all three verts together and divide... could use another determination + // of the 'depth' of the triangle such as min or max vert dist... + float[] pos = new float[]{(pt0[X] + pt1[X] + pt2[X]) /3, + (pt0[Y] + pt1[Y] + pt2[Y]) /3, + (pt0[Z] + pt1[Z] + pt2[Z]) /3}; + + // pt0, pt1 and pt2 are in eye coordinates since they have been + // multiplied by the modelview matrix. + float d = PApplet.dist(0f, 0f, 0f, pos[0], pos[1], pos[2]); + + Triangle tri = new Triangle(i0, i1, i2, textureImage, d); + sortedPolyTriangles[sortedTriangleCount] = tri; + sortedTriangleCount++; + } + } + } + quickSortTris(0, sortedTriangleCount - 1); + } + + // an 'in-place' implementation of quick I whipped together late at night + // based off of the algorithm found on wikipedia: http://en.wikipedia.org/wiki/Quicksort + private void quickSortTris(int leftI, int rightI) { + if (leftI < rightI) { + int pivotIndex = (leftI + rightI)/2; + int newPivotIndex = partition(leftI,rightI,pivotIndex); + quickSortTris(leftI, newPivotIndex-1); + quickSortTris(newPivotIndex+1, rightI); + } + } + + //part of quicksort + private int partition(int leftIndex, int rightIndex, int pivotIndex) { + float pivotVal = sortedPolyTriangles[pivotIndex].dist; + swapTris(pivotIndex,rightIndex); + int storeIndex = leftIndex; + for(int i = leftIndex; i < rightIndex; i++) + { + if(sortedPolyTriangles[i].dist > pivotVal) + { + swapTris(i,storeIndex); + storeIndex++; + } + } + swapTris(rightIndex,storeIndex); + return storeIndex; + } + + //part of quicksort + private void swapTris(int a, int b) { + Triangle tmp = sortedPolyTriangles[a]; + sortedPolyTriangles[a] = sortedPolyTriangles[b]; + sortedPolyTriangles[b] = tmp; + } + + + + void rawPolys() { + raw.colorMode(RGB); + raw.noStroke(); + raw.beginShape(TRIANGLES); + + + //sortTriangles(); + + float[] vertices = tessGeo.polyVertices; + int[] color = tessGeo.polyColors; + float[] uv = tessGeo.polyTexCoords; + short[] indices = tessGeo.polyIndices; // unused [fry] + +/* + sortTriangles(); + for (int i = 0; i < sortedTriangleCount; i++) { + Triangle tri = sortedPolyTriangles[i]; + int i0 = tri.i0; + int i1 = tri.i1; + int i2 = tri.i2; + PImage tex = tri.tex; + + float[] pt0 = {0, 0, 0, 0}; + float[] pt1 = {0, 0, 0, 0}; + float[] pt2 = {0, 0, 0, 0}; + int argb0 = PGL.nativeToJavaARGB(color[i0]); + int argb1 = PGL.nativeToJavaARGB(color[i1]); + int argb2 = PGL.nativeToJavaARGB(color[i2]); + + if (flushMode == FLUSH_CONTINUOUSLY) { + float[] src0 = {0, 0, 0, 0}; + float[] src1 = {0, 0, 0, 0}; + float[] src2 = {0, 0, 0, 0}; + PApplet.arrayCopy(vertices, 4 * i0, src0, 0, 4); + PApplet.arrayCopy(vertices, 4 * i1, src1, 0, 4); + PApplet.arrayCopy(vertices, 4 * i2, src2, 0, 4); + modelview.mult(src0, pt0); + modelview.mult(src1, pt1); + modelview.mult(src2, pt2); + } else { + PApplet.arrayCopy(vertices, 4 * i0, pt0, 0, 4); + PApplet.arrayCopy(vertices, 4 * i1, pt1, 0, 4); + PApplet.arrayCopy(vertices, 4 * i2, pt2, 0, 4); + } + + if (tex != null) { + raw.texture(tex); + if (raw.is3D()) { + raw.fill(argb0); + raw.vertex(pt0[X], pt0[Y], pt0[Z], uv[2 * i0 + 0], uv[2 * i0 + 1]); + raw.fill(argb1); + raw.vertex(pt1[X], pt1[Y], pt1[Z], uv[2 * i1 + 0], uv[2 * i1 + 1]); + raw.fill(argb2); + raw.vertex(pt2[X], pt2[Y], pt2[Z], uv[2 * i2 + 0], uv[2 * i2 + 1]); + } else if (raw.is2D()) { + float sx0 = screenXImpl(pt0[0], pt0[1], pt0[2], pt0[3]); + float sy0 = screenYImpl(pt0[0], pt0[1], pt0[2], pt0[3]); + float sx1 = screenXImpl(pt1[0], pt1[1], pt1[2], pt1[3]); + float sy1 = screenYImpl(pt1[0], pt1[1], pt1[2], pt1[3]); + float sx2 = screenXImpl(pt2[0], pt2[1], pt2[2], pt2[3]); + float sy2 = screenYImpl(pt2[0], pt2[1], pt2[2], pt2[3]); + raw.fill(argb0); + raw.vertex(sx0, sy0, uv[2 * i0 + 0], uv[2 * i0 + 1]); + raw.fill(argb1); + raw.vertex(sx1, sy1, uv[2 * i1 + 0], uv[2 * i1 + 1]); + raw.fill(argb1); + raw.vertex(sx2, sy2, uv[2 * i2 + 0], uv[2 * i2 + 1]); + } + } else { + if (raw.is3D()) { + raw.fill(argb0); + raw.vertex(pt0[X], pt0[Y], pt0[Z]); + raw.fill(argb1); + raw.vertex(pt1[X], pt1[Y], pt1[Z]); + raw.fill(argb2); + raw.vertex(pt2[X], pt2[Y], pt2[Z]); + } else if (raw.is2D()) { + float sx0 = screenXImpl(pt0[0], pt0[1], pt0[2], pt0[3]); + float sy0 = screenYImpl(pt0[0], pt0[1], pt0[2], pt0[3]); + float sx1 = screenXImpl(pt1[0], pt1[1], pt1[2], pt1[3]); + float sy1 = screenYImpl(pt1[0], pt1[1], pt1[2], pt1[3]); + float sx2 = screenXImpl(pt2[0], pt2[1], pt2[2], pt2[3]); + float sy2 = screenYImpl(pt2[0], pt2[1], pt2[2], pt2[3]); + raw.fill(argb0); + raw.vertex(sx0, sy0); + raw.fill(argb1); + raw.vertex(sx1, sy1); + raw.fill(argb2); + raw.vertex(sx2, sy2); + } + } + + } +*/ + + + for (int i = 0; i < texCache.size; i++) { + PImage textureImage = texCache.getTextureImage(i); + + int first = texCache.firstCache[i]; + int last = texCache.lastCache[i]; + IndexCache cache = tessGeo.polyIndexCache; + for (int n = first; n <= last; n++) { + int ioffset = n == first ? texCache.firstIndex[i] : + cache.indexOffset[n]; + int icount = n == last ? texCache.lastIndex[i] - ioffset + 1 : + cache.indexOffset[n] + cache.indexCount[n] - + ioffset; + int voffset = cache.vertexOffset[n]; + + for (int tr = ioffset / 3; tr < (ioffset + icount) / 3; tr++) { + int i0 = voffset + indices[3 * tr + 0]; + int i1 = voffset + indices[3 * tr + 1]; + int i2 = voffset + indices[3 * tr + 2]; + + float[] pt0 = {0, 0, 0, 0}; + float[] pt1 = {0, 0, 0, 0}; + float[] pt2 = {0, 0, 0, 0}; + int argb0 = PGL.nativeToJavaARGB(color[i0]); + int argb1 = PGL.nativeToJavaARGB(color[i1]); + int argb2 = PGL.nativeToJavaARGB(color[i2]); + + if (flushMode == FLUSH_CONTINUOUSLY) { + float[] src0 = {0, 0, 0, 0}; + float[] src1 = {0, 0, 0, 0}; + float[] src2 = {0, 0, 0, 0}; + PApplet.arrayCopy(vertices, 4 * i0, src0, 0, 4); + PApplet.arrayCopy(vertices, 4 * i1, src1, 0, 4); + PApplet.arrayCopy(vertices, 4 * i2, src2, 0, 4); + modelview.mult(src0, pt0); + modelview.mult(src1, pt1); + modelview.mult(src2, pt2); + } else { + PApplet.arrayCopy(vertices, 4 * i0, pt0, 0, 4); + PApplet.arrayCopy(vertices, 4 * i1, pt1, 0, 4); + PApplet.arrayCopy(vertices, 4 * i2, pt2, 0, 4); + } + + if (textureImage != null) { + raw.texture(textureImage); + if (raw.is3D()) { + raw.fill(argb0); + raw.vertex(pt0[X], pt0[Y], pt0[Z], uv[2 * i0 + 0], uv[2 * i0 + 1]); + raw.fill(argb1); + raw.vertex(pt1[X], pt1[Y], pt1[Z], uv[2 * i1 + 0], uv[2 * i1 + 1]); + raw.fill(argb2); + raw.vertex(pt2[X], pt2[Y], pt2[Z], uv[2 * i2 + 0], uv[2 * i2 + 1]); + } else if (raw.is2D()) { + float sx0 = screenXImpl(pt0[0], pt0[1], pt0[2], pt0[3]); + float sy0 = screenYImpl(pt0[0], pt0[1], pt0[2], pt0[3]); + float sx1 = screenXImpl(pt1[0], pt1[1], pt1[2], pt1[3]); + float sy1 = screenYImpl(pt1[0], pt1[1], pt1[2], pt1[3]); + float sx2 = screenXImpl(pt2[0], pt2[1], pt2[2], pt2[3]); + float sy2 = screenYImpl(pt2[0], pt2[1], pt2[2], pt2[3]); + raw.fill(argb0); + raw.vertex(sx0, sy0, uv[2 * i0 + 0], uv[2 * i0 + 1]); + raw.fill(argb1); + raw.vertex(sx1, sy1, uv[2 * i1 + 0], uv[2 * i1 + 1]); + raw.fill(argb1); + raw.vertex(sx2, sy2, uv[2 * i2 + 0], uv[2 * i2 + 1]); + } + } else { + if (raw.is3D()) { + raw.fill(argb0); + raw.vertex(pt0[X], pt0[Y], pt0[Z]); + raw.fill(argb1); + raw.vertex(pt1[X], pt1[Y], pt1[Z]); + raw.fill(argb2); + raw.vertex(pt2[X], pt2[Y], pt2[Z]); + } else if (raw.is2D()) { + float sx0 = screenXImpl(pt0[0], pt0[1], pt0[2], pt0[3]); + float sy0 = screenYImpl(pt0[0], pt0[1], pt0[2], pt0[3]); + float sx1 = screenXImpl(pt1[0], pt1[1], pt1[2], pt1[3]); + float sy1 = screenYImpl(pt1[0], pt1[1], pt1[2], pt1[3]); + float sx2 = screenXImpl(pt2[0], pt2[1], pt2[2], pt2[3]); + float sy2 = screenYImpl(pt2[0], pt2[1], pt2[2], pt2[3]); + raw.fill(argb0); + raw.vertex(sx0, sy0); + raw.fill(argb1); + raw.vertex(sx1, sy1); + raw.fill(argb2); + raw.vertex(sx2, sy2); + } + } + } + } + } + + + raw.endShape(); + } + + + protected void flushLines() { + updateLineBuffers(); + + PShader shader = getLineShader(); + shader.bind(); + + IndexCache cache = tessGeo.lineIndexCache; + for (int n = 0; n < cache.size; n++) { + int ioffset = cache.indexOffset[n]; + int icount = cache.indexCount[n]; + int voffset = cache.vertexOffset[n]; + + shader.setVertexAttribute(glLineVertex, 4, PGL.FLOAT, 0, + 4 * voffset * PGL.SIZEOF_FLOAT); + shader.setColorAttribute(glLineColor, 4, PGL.UNSIGNED_BYTE, 0, + 4 * voffset * PGL.SIZEOF_BYTE); + shader.setLineAttribute(glLineAttrib, 4, PGL.FLOAT, 0, + 4 * voffset * PGL.SIZEOF_FLOAT); + + shader.draw(glLineIndex, icount, ioffset); + } + + shader.unbind(); + unbindLineBuffers(); + } + + + void rawLines() { + raw.colorMode(RGB); + raw.noFill(); + raw.strokeCap(strokeCap); + raw.strokeJoin(strokeJoin); + raw.beginShape(LINES); + + float[] vertices = tessGeo.lineVertices; + int[] color = tessGeo.lineColors; + float[] attribs = tessGeo.lineDirections; + short[] indices = tessGeo.lineIndices; + + IndexCache cache = tessGeo.lineIndexCache; + for (int n = 0; n < cache.size; n++) { + int ioffset = cache.indexOffset[n]; + int icount = cache.indexCount[n]; + int voffset = cache.vertexOffset[n]; + + for (int ln = ioffset / 6; ln < (ioffset + icount) / 6; ln++) { + // Each line segment is defined by six indices since its + // formed by two triangles. We only need the first and last + // vertices. + // This bunch of vertices could also be the bevel triangles, + // with we detect this situation by looking at the line weight. + int i0 = voffset + indices[6 * ln + 0]; + int i1 = voffset + indices[6 * ln + 5]; + float sw0 = 2 * attribs[4 * i0 + 3]; + float sw1 = 2 * attribs[4 * i1 + 3]; + + if (zero(sw0)) continue; // Bevel triangles, skip. + + float[] pt0 = {0, 0, 0, 0}; + float[] pt1 = {0, 0, 0, 0}; + int argb0 = PGL.nativeToJavaARGB(color[i0]); + int argb1 = PGL.nativeToJavaARGB(color[i1]); + + if (flushMode == FLUSH_CONTINUOUSLY) { + float[] src0 = {0, 0, 0, 0}; + float[] src1 = {0, 0, 0, 0}; + PApplet.arrayCopy(vertices, 4 * i0, src0, 0, 4); + PApplet.arrayCopy(vertices, 4 * i1, src1, 0, 4); + modelview.mult(src0, pt0); + modelview.mult(src1, pt1); + } else { + PApplet.arrayCopy(vertices, 4 * i0, pt0, 0, 4); + PApplet.arrayCopy(vertices, 4 * i1, pt1, 0, 4); + } + + if (raw.is3D()) { + raw.strokeWeight(sw0); + raw.stroke(argb0); + raw.vertex(pt0[X], pt0[Y], pt0[Z]); + raw.strokeWeight(sw1); + raw.stroke(argb1); + raw.vertex(pt1[X], pt1[Y], pt1[Z]); + } else if (raw.is2D()) { + float sx0 = screenXImpl(pt0[0], pt0[1], pt0[2], pt0[3]); + float sy0 = screenYImpl(pt0[0], pt0[1], pt0[2], pt0[3]); + float sx1 = screenXImpl(pt1[0], pt1[1], pt1[2], pt1[3]); + float sy1 = screenYImpl(pt1[0], pt1[1], pt1[2], pt1[3]); + raw.strokeWeight(sw0); + raw.stroke(argb0); + raw.vertex(sx0, sy0); + raw.strokeWeight(sw1); + raw.stroke(argb1); + raw.vertex(sx1, sy1); + } + } + } + + raw.endShape(); + } + + + protected void flushPoints() { + updatePointBuffers(); + + PShader shader = getPointShader(); + shader.bind(); + + IndexCache cache = tessGeo.pointIndexCache; + for (int n = 0; n < cache.size; n++) { + int ioffset = cache.indexOffset[n]; + int icount = cache.indexCount[n]; + int voffset = cache.vertexOffset[n]; + + shader.setVertexAttribute(glPointVertex, 4, PGL.FLOAT, 0, + 4 * voffset * PGL.SIZEOF_FLOAT); + shader.setColorAttribute(glPointColor, 4, PGL.UNSIGNED_BYTE, 0, + 4 * voffset * PGL.SIZEOF_BYTE); + shader.setPointAttribute(glPointAttrib, 2, PGL.FLOAT, 0, + 2 * voffset * PGL.SIZEOF_FLOAT); + + shader.draw(glPointIndex, icount, ioffset); + } + + shader.unbind(); + unbindPointBuffers(); + } + + + void rawPoints() { + raw.colorMode(RGB); + raw.noFill(); + raw.strokeCap(strokeCap); + raw.beginShape(POINTS); + + float[] vertices = tessGeo.pointVertices; + int[] color = tessGeo.pointColors; + float[] attribs = tessGeo.pointOffsets; + short[] indices = tessGeo.pointIndices; + + IndexCache cache = tessGeo.pointIndexCache; + for (int n = 0; n < cache.size; n++) { + int ioffset = cache.indexOffset[n]; + int icount = cache.indexCount[n]; + int voffset = cache.vertexOffset[n]; + + int pt = ioffset; + while (pt < (ioffset + icount) / 3) { + float size = attribs[2 * pt + 2]; + float weight; + int perim; + if (0 < size) { // round point + weight = +size / 0.5f; + perim = PApplet.min(MAX_POINT_ACCURACY, PApplet.max(MIN_POINT_ACCURACY, + (int) (TWO_PI * weight / POINT_ACCURACY_FACTOR))) + 1; + } else { // Square point + weight = -size / 0.5f; + perim = 5; + } + + int i0 = voffset + indices[3 * pt]; + int argb0 = PGL.nativeToJavaARGB(color[i0]); + float[] pt0 = {0, 0, 0, 0}; + + if (flushMode == FLUSH_CONTINUOUSLY) { + float[] src0 = {0, 0, 0, 0}; + PApplet.arrayCopy(vertices, 4 * i0, src0, 0, 4); + modelview.mult(src0, pt0); + } else { + PApplet.arrayCopy(vertices, 4 * i0, pt0, 0, 4); + } + + if (raw.is3D()) { + raw.strokeWeight(weight); + raw.stroke(argb0); + raw.vertex(pt0[X], pt0[Y], pt0[Z]); + } else if (raw.is2D()) { + float sx0 = screenXImpl(pt0[0], pt0[1], pt0[2], pt0[3]); + float sy0 = screenYImpl(pt0[0], pt0[1], pt0[2], pt0[3]); + raw.strokeWeight(weight); + raw.stroke(argb0); + raw.vertex(sx0, sy0); + } + + pt += perim; + } + } + + raw.endShape(); + } + + + ////////////////////////////////////////////////////////////// + + // BEZIER CURVE VERTICES + + + @Override + public void bezierVertex(float x2, float y2, + float x3, float y3, + float x4, float y4) { + bezierVertexImpl(x2, y2, 0, + x3, y3, 0, + x4, y4, 0); + } + + + @Override + public void bezierVertex(float x2, float y2, float z2, + float x3, float y3, float z3, + float x4, float y4, float z4) { + bezierVertexImpl(x2, y2, z2, + x3, y3, z3, + x4, y4, z4); + } + + + protected void bezierVertexImpl(float x2, float y2, float z2, + float x3, float y3, float z3, + float x4, float y4, float z4) { + bezierVertexCheck(shape, inGeo.vertexCount); + inGeo.setMaterial(fillColor, strokeColor, strokeWeight, + ambientColor, specularColor, emissiveColor, shininess); + inGeo.setNormal(normalX, normalY, normalZ); + inGeo.addBezierVertex(x2, y2, z2, + x3, y3, z3, + x4, y4, z4, vertexBreak()); + } + + + @Override + public void quadraticVertex(float cx, float cy, + float x3, float y3) { + quadraticVertexImpl(cx, cy, 0, + x3, y3, 0); + } + + + @Override + public void quadraticVertex(float cx, float cy, float cz, + float x3, float y3, float z3) { + quadraticVertexImpl(cx, cy, cz, + x3, y3, z3); + } + + + protected void quadraticVertexImpl(float cx, float cy, float cz, + float x3, float y3, float z3) { + bezierVertexCheck(shape, inGeo.vertexCount); + inGeo.setMaterial(fillColor, strokeColor, strokeWeight, + ambientColor, specularColor, emissiveColor, shininess); + inGeo.setNormal(normalX, normalY, normalZ); + inGeo.addQuadraticVertex(cx, cy, cz, + x3, y3, z3, vertexBreak()); + } + + + ////////////////////////////////////////////////////////////// + + // CATMULL-ROM CURVE VERTICES + + + @Override + public void curveVertex(float x, float y) { + curveVertexImpl(x, y, 0); + } + + + @Override + public void curveVertex(float x, float y, float z) { + curveVertexImpl(x, y, z); + } + + + protected void curveVertexImpl(float x, float y, float z) { + curveVertexCheck(shape); + inGeo.setMaterial(fillColor, strokeColor, strokeWeight, + ambientColor, specularColor, emissiveColor, shininess); + inGeo.setNormal(normalX, normalY, normalZ); + inGeo.addCurveVertex(x, y, z, vertexBreak()); + } + + + ////////////////////////////////////////////////////////////// + + // POINT, LINE, TRIANGLE, QUAD + + + @Override + public void point(float x, float y) { + pointImpl(x, y, 0); + } + + + @Override + public void point(float x, float y, float z) { + pointImpl(x, y, z); + } + + + protected void pointImpl(float x, float y, float z) { + beginShape(POINTS); + defaultEdges = false; + normalMode = NORMAL_MODE_SHAPE; + inGeo.setMaterial(fillColor, strokeColor, strokeWeight, + ambientColor, specularColor, emissiveColor, shininess); + inGeo.setNormal(normalX, normalY, normalZ); + inGeo.addPoint(x, y, z, fill, stroke); + endShape(); + } + + + @Override + public void line(float x1, float y1, float x2, float y2) { + lineImpl(x1, y1, 0, x2, y2, 0); + } + + + @Override + public void line(float x1, float y1, float z1, + float x2, float y2, float z2) { + lineImpl(x1, y1, z1, x2, y2, z2); + } + + + protected void lineImpl(float x1, float y1, float z1, + float x2, float y2, float z2) { + beginShape(LINES); + defaultEdges = false; + normalMode = NORMAL_MODE_SHAPE; + inGeo.setMaterial(fillColor, strokeColor, strokeWeight, + ambientColor, specularColor, emissiveColor, shininess); + inGeo.setNormal(normalX, normalY, normalZ); + inGeo.addLine(x1, y1, z1, + x2, y2, z2, + fill, stroke); + endShape(); + } + + + @Override + public void triangle(float x1, float y1, float x2, float y2, + float x3, float y3) { + beginShape(TRIANGLES); + defaultEdges = false; + normalMode = NORMAL_MODE_SHAPE; + inGeo.setMaterial(fillColor, strokeColor, strokeWeight, + ambientColor, specularColor, emissiveColor, shininess); + inGeo.setNormal(normalX, normalY, normalZ); + inGeo.addTriangle(x1, y1, 0, + x2, y2, 0, + x3, y3, 0, + fill, stroke); + endShape(); + } + + + @Override + public void quad(float x1, float y1, float x2, float y2, + float x3, float y3, float x4, float y4) { + beginShape(QUADS); + defaultEdges = false; + normalMode = NORMAL_MODE_SHAPE; + inGeo.setMaterial(fillColor, strokeColor, strokeWeight, + ambientColor, specularColor, emissiveColor, shininess); + inGeo.setNormal(normalX, normalY, normalZ); + inGeo.addQuad(x1, y1, 0, + x2, y2, 0, + x3, y3, 0, + x4, y4, 0, + stroke); + endShape(); + } + + + @Override + protected void rectImpl(float x1, float y1, float x2, float y2, + float tl, float tr, float br, float bl) { + beginShape(POLYGON); + defaultEdges = false; + normalMode = NORMAL_MODE_SHAPE; + inGeo.setMaterial(fillColor, strokeColor, strokeWeight, + ambientColor, specularColor, emissiveColor, shininess); + inGeo.setNormal(normalX, normalY, normalZ); + inGeo.addRect(x1, y1, x2, y2, tl, tr, br, bl, stroke); + endShape(CLOSE); + } + + + ////////////////////////////////////////////////////////////// + + // ELLIPSE + + + @Override + public void ellipseImpl(float a, float b, float c, float d) { + beginShape(TRIANGLE_FAN); + defaultEdges = false; + normalMode = NORMAL_MODE_SHAPE; + inGeo.setMaterial(fillColor, strokeColor, strokeWeight, + ambientColor, specularColor, emissiveColor, shininess); + inGeo.setNormal(normalX, normalY, normalZ); + inGeo.addEllipse(a, b, c, d, fill, stroke); + endShape(); + } + + + @Override + protected void arcImpl(float x, float y, float w, float h, + float start, float stop, int mode) { + beginShape(TRIANGLE_FAN); + defaultEdges = false; + normalMode = NORMAL_MODE_SHAPE; + inGeo.setMaterial(fillColor, strokeColor, strokeWeight, + ambientColor, specularColor, emissiveColor, shininess); + + inGeo.setNormal(normalX, normalY, normalZ); + inGeo.addArc(x, y, w, h, start, stop, fill, stroke, mode); + endShape(); + } + + + ////////////////////////////////////////////////////////////// + + // BOX + + // public void box(float size) + + @Override + public void box(float w, float h, float d) { + beginShape(QUADS); + defaultEdges = false; + normalMode = NORMAL_MODE_VERTEX; + inGeo.setMaterial(fillColor, strokeColor, strokeWeight, + ambientColor, specularColor, emissiveColor, shininess); + inGeo.addBox(w, h, d, fill, stroke); + endShape(); + } + + ////////////////////////////////////////////////////////////// + + // SPHERE + + // public void sphereDetail(int res) + + // public void sphereDetail(int ures, int vres) + + @Override + public void sphere(float r) { + if ((sphereDetailU < 3) || (sphereDetailV < 2)) { + sphereDetail(30); + } + + beginShape(TRIANGLES); + defaultEdges = false; + normalMode = NORMAL_MODE_VERTEX; + inGeo.setMaterial(fillColor, strokeColor, strokeWeight, + ambientColor, specularColor, emissiveColor, shininess); + int[] indices = inGeo.addSphere(r, sphereDetailU, sphereDetailV, + fill, stroke); + endShape(indices); + } + + ////////////////////////////////////////////////////////////// + + // BEZIER + + // public float bezierPoint(float a, float b, float c, float d, float t) + + // public float bezierTangent(float a, float b, float c, float d, float t) + + // public void bezierDetail(int detail) + + // public void bezier(float x1, float y1, + // float x2, float y2, + // float x3, float y3, + // float x4, float y4) + + // 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) + + ////////////////////////////////////////////////////////////// + + // CATMULL-ROM CURVES + + // public float curvePoint(float a, float b, float c, float d, float t) + + // public float curveTangent(float a, float b, float c, float d, float t) + + // public void curveDetail(int detail) + + // public void curveTightness(float tightness) + + // public void curve(float x1, float y1, + // float x2, float y2, + // float x3, float y3, + // float x4, float y4) + + // 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) + + ////////////////////////////////////////////////////////////// + + // IMAGES + + // public void imageMode(int mode) + + // public void image(PImage image, float x, float y) + + // public void image(PImage image, float x, float y, float c, float d) + + // public void image(PImage image, + // float a, float b, float c, float d, + // int u1, int v1, int u2, int v2) + + // protected void imageImpl(PImage image, + // float x1, float y1, float x2, float y2, + // int u1, int v1, int u2, int v2) + + ////////////////////////////////////////////////////////////// + + // SMOOTH + + + @Override + public void smooth() { + if (quality < 2) { + smooth(2); + } else { + smooth(quality); + } + } + + + @Override + public void smooth(int level) { + if (smoothDisabled || PGL.MAX_SAMPLES == -1) return; + + smooth = true; + + if (maxSamples < level) { + if (0 < maxSamples) { + PGraphics.showWarning(UNSUPPORTED_SMOOTH_LEVEL_ERROR, level, maxSamples); + } else{ + PGraphics.showWarning(UNSUPPORTED_SMOOTH_ERROR); + } + level = maxSamples; + } + + if (quality != level) { + smoothCallCount++; + if (parent.frameCount - lastSmoothCall < 30 && 5 < smoothCallCount) { + smoothDisabled = true; + PGraphics.showWarning(TOO_MANY_SMOOTH_CALLS_ERROR); + } + lastSmoothCall = parent.frameCount; + + quality = level; + System.out.println(quality); + + if (quality == 1) { + quality = 0; + } + + // This will trigger a surface restart next time + // requestDraw() is called. +// restartPGL(); + } + } + + + @Override + public void noSmooth() { + if (smoothDisabled) return; + + smooth = false; + + if (1 < quality) { + smoothCallCount++; + if (parent.frameCount - lastSmoothCall < 30 && 5 < smoothCallCount) { + smoothDisabled = true; + PGraphics.showWarning(TOO_MANY_SMOOTH_CALLS_ERROR); + } + lastSmoothCall = parent.frameCount; + + quality = 0; + + // This will trigger a surface restart next time + // requestDraw() is called. + restartPGL(); + } + } + + + ////////////////////////////////////////////////////////////// + + // SHAPE + + // public void shapeMode(int mode) + + + // TODO unapproved + @Override + protected void shape(PShape shape, float x, float y, float z) { + if (shape.isVisible()) { // don't do expensive matrix ops if invisible + flush(); + + pushMatrix(); + + if (shapeMode == CENTER) { + translate(x - shape.getWidth() / 2, y - shape.getHeight() / 2, + z - shape.getDepth() / 2); + + } else if ((shapeMode == CORNER) || (shapeMode == CORNERS)) { + translate(x, y, z); + } + shape.draw(this); + + popMatrix(); + } + } + + + // TODO unapproved + @Override + protected void shape(PShape shape, float x, float y, float z, + float c, float d, float e) { + if (shape.isVisible()) { // don't do expensive matrix ops if invisible + flush(); + + pushMatrix(); + + if (shapeMode == CENTER) { + // x, y and z are center, c, d and e refer to a diameter + translate(x - c / 2f, y - d / 2f, z - e / 2f); + scale(c / shape.getWidth(), + d / shape.getHeight(), + e / shape.getDepth()); + + } else if (shapeMode == CORNER) { + translate(x, y, z); + scale(c / shape.getWidth(), + d / shape.getHeight(), + e / shape.getDepth()); + + } else if (shapeMode == CORNERS) { + // c, d, e are x2/y2/z2, make them into width/height/depth + c -= x; + d -= y; + e -= z; + // then same as above + translate(x, y, z); + scale(c / shape.getWidth(), + d / shape.getHeight(), + e / shape.getDepth()); + } + shape.draw(this); + + popMatrix(); + } + } + + + ////////////////////////////////////////////////////////////// + + // SHAPE I/O + + + @Override + public PShape loadShape(String filename) { + String ext = PApplet.getExtension(filename); + if (PGraphics2D.isSupportedExtension(ext)) { + return PGraphics2D.loadShapeImpl(this, filename, ext); + } if (PGraphics3D.isSupportedExtension(ext)) { + return PGraphics3D.loadShapeImpl(this, filename, ext); + } else { + PGraphics.showWarning(UNSUPPORTED_SHAPE_FORMAT_ERROR); + return null; + } + } + + + ////////////////////////////////////////////////////////////// + + // TEXT SETTINGS + + // public void textAlign(int align) + + // public void textAlign(int alignX, int alignY) + + // public float textAscent() + + // public float textDescent() + + // public void textFont(PFont which) + + // public void textFont(PFont which, float size) + + // public void textLeading(float leading) + + // public void textMode(int mode) + + @Override + protected boolean textModeCheck(int mode) { + return mode == MODEL || (mode == SHAPE && PGL.SHAPE_TEXT_SUPPORTED); + } + + // public void textSize(float size) + + // public float textWidth(char c) + + // public float textWidth(String str) + + // protected float textWidthImpl(char buffer[], int start, int stop) + + + ////////////////////////////////////////////////////////////// + + // TEXT IMPL + + + @Override + public float textAscent() { + if (textFont == null) defaultFontOrDeath("textAscent"); + Font font = (Font) textFont.getNative(); + float ascent = 0; + if (font != null) ascent = pgl.getFontAscent(font); + if (ascent == 0) ascent = super.textAscent(); + return ascent; + } + + + @Override + public float textDescent() { + if (textFont == null) defaultFontOrDeath("textAscent"); + Font font = (Font) textFont.getNative(); + float descent = 0; + if (font != null) descent = pgl.getFontDescent(font); + if (descent == 0) descent = super.textDescent(); + return descent; + } + + + @Override + protected float textWidthImpl(char buffer[], int start, int stop) { + Font font = (Font) textFont.getNative(); + float twidth = 0; + if (font != null) twidth = pgl.getTextWidth(font, buffer, start, stop); + if (twidth == 0) twidth = super.textWidthImpl(buffer, start, stop); + return twidth; + } + + + @Override + public void textSize(float size) { + if (textFont == null) defaultFontOrDeath("textSize", size); + Font font = (Font) textFont.getNative(); + if (font != null) { + Object dfont = pgl.getDerivedFont(font, size); + textFont.setNative(dfont); + } + super.textSize(size); + } + + + /** + * Implementation of actual drawing for a line of text. + */ + @Override + protected void textLineImpl(char buffer[], int start, int stop, + float x, float y) { + if (textMode == MODEL) { + textTex = getFontTexture(textFont); + + if (textTex == null || textTex.contextIsOutdated()) { + textTex = new FontTexture(this, textFont, is3D()); + setFontTexture(textFont, textTex); + } + + textTex.begin(); + + // Saving style parameters modified by text rendering. + int savedTextureMode = textureMode; + boolean savedStroke = stroke; + float savedNormalX = normalX; + float savedNormalY = normalY; + float savedNormalZ = normalZ; + boolean savedTint = tint; + int savedTintColor = tintColor; + int savedBlendMode = blendMode; + + // Setting style used in text rendering. + textureMode = NORMAL; + stroke = false; + normalX = 0; + normalY = 0; + normalZ = 1; + tint = true; + tintColor = fillColor; + + blendMode(BLEND); + + super.textLineImpl(buffer, start, stop, x, y); + + // Restoring original style. + textureMode = savedTextureMode; + stroke = savedStroke; + normalX = savedNormalX; + normalY = savedNormalY; + normalZ = savedNormalZ; + tint = savedTint; + tintColor = savedTintColor; + + // Note that if the user is using a blending mode different from + // BLEND, and has a bunch of continuous text rendering, the performance + // won't be optimal because at the end of each text() call the geometry + // will be flushed when restoring the user's blend. + blendMode(savedBlendMode); + + textTex.end(); + } else if (textMode == SHAPE) { + super.textLineImpl(buffer, start, stop, x, y); + } + } + + + @Override + protected void textCharImpl(char ch, float x, float y) { + PFont.Glyph glyph = textFont.getGlyph(ch); + if (glyph != null) { + if (textMode == MODEL) { + FontTexture.TextureInfo tinfo = textTex.getTexInfo(glyph); + + if (tinfo == null) { + // Adding new glyph to the font texture. + tinfo = textTex.addToTexture(this, glyph); + } + + float high = glyph.height / (float) textFont.getSize(); + float bwidth = glyph.width / (float) textFont.getSize(); + float lextent = glyph.leftExtent / (float) textFont.getSize(); + float textent = glyph.topExtent / (float) textFont.getSize(); + + float x1 = x + lextent * textSize; + float y1 = y - textent * textSize; + float x2 = x1 + bwidth * textSize; + float y2 = y1 + high * textSize; + + textCharModelImpl(tinfo, x1, y1, x2, y2); + } else if (textMode == SHAPE) { + textCharShapeImpl(ch, x, y); + } + } + } + + + protected void textCharModelImpl(FontTexture.TextureInfo info, + float x0, float y0, + float x1, float y1) { + if (textTex.currentTex != info.texIndex) { + textTex.setTexture(info.texIndex); + } + beginShape(QUADS); + texture(textTex.getCurrentTexture()); + vertex(x0, y0, info.u0, info.v0); + vertex(x1, y0, info.u1, info.v0); + vertex(x1, y1, info.u1, info.v1); + vertex(x0, y1, info.u0, info.v1); + endShape(); + } + + + /** + * Ported from the implementation of textCharShapeImpl() in 1.5.1 + * + * No attempt has been made to optimize this code + *

+ * TODO: Implement a FontShape class where each glyph is tessellated and + * stored inside a larger PShapeOpenGL object (which needs to be expanded as + * new glyphs are added and exceed the initial capacity in a similar way as + * the textures in FontTexture work). When a string of text is to be rendered + * in shape mode, then the correct sequences of vertex indices are computed + * (akin to the texcoords in the texture case) and used to draw only those + * parts of the PShape object that are required for the text. + *

+ * + * Some issues of the original implementation probably remain, so they are + * reproduced below: + *

+ * Also a problem where some fonts seem to be a bit slight, as if the + * control points aren't being mapped quite correctly. Probably doing + * something dumb that the control points don't map to P5's control + * points. Perhaps it's returning b-spline data from the TrueType font? + * Though it seems like that would make a lot of garbage rather than + * just a little flattening. + *

+ * There also seems to be a bug that is causing a line (but not a filled + * triangle) back to the origin on some letters (i.e. a capital L when + * tested with Akzidenz Grotesk Light). But this won't be visible + * with the stroke shut off, so tabling that bug for now. + */ + protected void textCharShapeImpl(char ch, float x, float y) { + // save the current stroke because it needs to be disabled + // while the text is being drawn + boolean strokeSaved = stroke; + stroke = false; + + PGL.FontOutline outline = pgl.createFontOutline(ch, textFont.getNative()); + + // six element array received from the Java2D path iterator + float textPoints[] = new float[6]; + float lastX = 0; + float lastY = 0; + + boolean open = false; + beginShape(); + while (!outline.isDone()) { + int type = outline.currentSegment(textPoints); + if (!open) { + beginContour(); + open = true; + } + if (type == PGL.SEG_MOVETO || type == PGL.SEG_LINETO) { // 1 point + vertex(x + textPoints[0], y + textPoints[1]); + lastX = textPoints[0]; + lastY = textPoints[1]; + } else if (type == PGL.SEG_QUADTO) { // 2 points + for (int i = 1; i < bezierDetail; i++) { + float t = (float)i / (float)bezierDetail; + vertex(x + bezierPoint(lastX, + lastX + (float) ((textPoints[0] - lastX) * 2/3.0), + textPoints[2] + (float) ((textPoints[0] - textPoints[2]) * 2/3.0), + textPoints[2], t), + y + bezierPoint(lastY, + lastY + (float) ((textPoints[1] - lastY) * 2/3.0), + textPoints[3] + (float) ((textPoints[1] - textPoints[3]) * 2/3.0), + textPoints[3], t)); + } + lastX = textPoints[2]; + lastY = textPoints[3]; + } else if (type == PGL.SEG_CUBICTO) { // 3 points + for (int i = 1; i < bezierDetail; i++) { + float t = (float)i / (float)bezierDetail; + vertex(x + bezierPoint(lastX, textPoints[0], + textPoints[2], textPoints[4], t), + y + bezierPoint(lastY, textPoints[1], + textPoints[3], textPoints[5], t)); + } + lastX = textPoints[4]; + lastY = textPoints[5]; + } else if (type == PGL.SEG_CLOSE) { + endContour(); + open = false; + } + outline.next(); + } + endShape(); + + // re-enable stroke if it was in use before + stroke = strokeSaved; + } + + + ////////////////////////////////////////////////////////////// + + // MATRIX STACK + + + @Override + public void pushMatrix() { + if (modelviewStackDepth == MATRIX_STACK_DEPTH) { + throw new RuntimeException(ERROR_PUSHMATRIX_OVERFLOW); + } + modelview.get(modelviewStack[modelviewStackDepth]); + modelviewInv.get(modelviewInvStack[modelviewStackDepth]); + camera.get(cameraStack[modelviewStackDepth]); + cameraInv.get(cameraInvStack[modelviewStackDepth]); + modelviewStackDepth++; + } + + + @Override + public void popMatrix() { + if (modelviewStackDepth == 0) { + throw new RuntimeException(ERROR_PUSHMATRIX_UNDERFLOW); + } + modelviewStackDepth--; + modelview.set(modelviewStack[modelviewStackDepth]); + modelviewInv.set(modelviewInvStack[modelviewStackDepth]); + camera.set(cameraStack[modelviewStackDepth]); + cameraInv.set(cameraInvStack[modelviewStackDepth]); + updateProjmodelview(); + } + + + ////////////////////////////////////////////////////////////// + + // MATRIX TRANSFORMATIONS + + + @Override + public void translate(float tx, float ty) { + translateImpl(tx, ty, 0); + } + + + @Override + public void translate(float tx, float ty, float tz) { + translateImpl(tx, ty, tz); + } + + + protected void translateImpl(float tx, float ty, float tz) { + modelview.translate(tx, ty, tz); + invTranslate(modelviewInv, tx, ty, tz); + projmodelview.translate(tx, ty, tz); + } + + + static protected void invTranslate(PMatrix3D matrix, + float tx, float ty, float tz) { + matrix.preApply(1, 0, 0, -tx, + 0, 1, 0, -ty, + 0, 0, 1, -tz, + 0, 0, 0, 1); + } + + + /** + * Two dimensional rotation. Same as rotateZ (this is identical to a 3D + * rotation along the z-axis) but included for clarity -- it'd be weird for + * people drawing 2D graphics to be using rotateZ. And they might kick our a-- + * for the confusion. + */ + @Override + public void rotate(float angle) { + rotateImpl(angle, 0, 0, 1); + } + + + @Override + public void rotateX(float angle) { + rotateImpl(angle, 1, 0, 0); + } + + + @Override + public void rotateY(float angle) { + rotateImpl(angle, 0, 1, 0); + } + + + @Override + public void rotateZ(float angle) { + rotateImpl(angle, 0, 0, 1); + } + + + /** + * Rotate around an arbitrary vector, similar to glRotate(), except that it + * takes radians (instead of degrees). + */ + @Override + public void rotate(float angle, float v0, float v1, float v2) { + rotateImpl(angle, v0, v1, v2); + } + + + protected void rotateImpl(float angle, float v0, float v1, float v2) { + float norm2 = v0 * v0 + v1 * v1 + v2 * v2; + if (zero(norm2)) { + // The vector is zero, cannot apply rotation. + return; + } + + if (diff(norm2, 1)) { + // The rotation vector is not normalized. + float norm = PApplet.sqrt(norm2); + v0 /= norm; + v1 /= norm; + v2 /= norm; + } + + modelview.rotate(angle, v0, v1, v2); + invRotate(modelviewInv, angle, v0, v1, v2); + updateProjmodelview(); // Possibly cheaper than doing projmodelview.rotate() + } + + + static private void invRotate(PMatrix3D matrix, float angle, + float v0, float v1, float v2) { + float c = PApplet.cos(-angle); + float s = PApplet.sin(-angle); + float t = 1.0f - c; + + matrix.preApply((t*v0*v0) + c, (t*v0*v1) - (s*v2), (t*v0*v2) + (s*v1), 0, + (t*v0*v1) + (s*v2), (t*v1*v1) + c, (t*v1*v2) - (s*v0), 0, + (t*v0*v2) - (s*v1), (t*v1*v2) + (s*v0), (t*v2*v2) + c, 0, + 0, 0, 0, 1); + } + + + /** + * Same as scale(s, s, s). + */ + @Override + public void scale(float s) { + scaleImpl(s, s, s); + } + + + /** + * Same as scale(sx, sy, 1). + */ + @Override + public void scale(float sx, float sy) { + scaleImpl(sx, sy, 1); + } + + + /** + * Scale in three dimensions. + */ + @Override + public void scale(float sx, float sy, float sz) { + scaleImpl(sx, sy, sz); + } + + /** + * Scale in three dimensions. + */ + protected void scaleImpl(float sx, float sy, float sz) { + modelview.scale(sx, sy, sz); + invScale(modelviewInv, sx, sy, sz); + projmodelview.scale(sx, sy, sz); + } + + + static protected void invScale(PMatrix3D matrix, float x, float y, float z) { + matrix.preApply(1/x, 0, 0, 0, 0, 1/y, 0, 0, 0, 0, 1/z, 0, 0, 0, 0, 1); + } + + + @Override + public void shearX(float angle) { + float t = (float) Math.tan(angle); + applyMatrixImpl(1, t, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1); + } + + + @Override + public void shearY(float angle) { + float t = (float) Math.tan(angle); + applyMatrixImpl(1, 0, 0, 0, + t, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1); + } + + + ////////////////////////////////////////////////////////////// + + // MATRIX MORE! + + + @Override + public void resetMatrix() { + modelview.reset(); + modelviewInv.reset(); + projmodelview.set(projection); + + // For consistency, since modelview = camera * [all other transformations] + // the camera matrix should be set to the identity as well: + camera.reset(); + cameraInv.reset(); + } + + + @Override + public void applyMatrix(PMatrix2D source) { + applyMatrixImpl(source.m00, source.m01, 0, source.m02, + source.m10, source.m11, 0, source.m12, + 0, 0, 1, 0, + 0, 0, 0, 1); + } + + + @Override + public void applyMatrix(float n00, float n01, float n02, + float n10, float n11, float n12) { + applyMatrixImpl(n00, n01, 0, n02, + n10, n11, 0, n12, + 0, 0, 1, 0, + 0, 0, 0, 1); + } + + + @Override + public void applyMatrix(PMatrix3D source) { + applyMatrixImpl(source.m00, source.m01, source.m02, source.m03, + source.m10, source.m11, source.m12, source.m13, + source.m20, source.m21, source.m22, source.m23, + source.m30, source.m31, source.m32, source.m33); + } + + + /** + * Apply a 4x4 transformation matrix to the modelview stack. + */ + @Override + public void applyMatrix(float n00, float n01, float n02, float n03, + float n10, float n11, float n12, float n13, + float n20, float n21, float n22, float n23, + float n30, float n31, float n32, float n33) { + applyMatrixImpl(n00, n01, n02, n03, + n10, n11, n12, n13, + n20, n21, n22, n23, + n30, n31, n32, n33); + } + + + protected void applyMatrixImpl(float n00, float n01, float n02, float n03, + float n10, float n11, float n12, float n13, + float n20, float n21, float n22, float n23, + float n30, float n31, float n32, float n33) { + modelview.apply(n00, n01, n02, n03, + n10, n11, n12, n13, + n20, n21, n22, n23, + n30, n31, n32, n33); + modelviewInv.set(modelview); + modelviewInv.invert(); + + projmodelview.apply(n00, n01, n02, n03, + n10, n11, n12, n13, + n20, n21, n22, n23, + n30, n31, n32, n33); + } + + + protected void begin2D() { + } + + + protected void end2D() { + } + + + ////////////////////////////////////////////////////////////// + + // MATRIX GET/SET/PRINT + + + @Override + public PMatrix getMatrix() { + return modelview.get(); + } + + + // public PMatrix2D getMatrix(PMatrix2D target) + + + @Override + public PMatrix3D getMatrix(PMatrix3D target) { + if (target == null) { + target = new PMatrix3D(); + } + target.set(modelview); + return target; + } + + + // public void setMatrix(PMatrix source) + + + @Override + public void setMatrix(PMatrix2D source) { + resetMatrix(); + applyMatrix(source); + } + + + /** + * Set the current transformation to the contents of the specified source. + */ + @Override + public void setMatrix(PMatrix3D source) { + resetMatrix(); + applyMatrix(source); + } + + + /** + * Print the current model (or "transformation") matrix. + */ + @Override + public void printMatrix() { + modelview.print(); + } + + + ////////////////////////////////////////////////////////////// + + // PROJECTION + + + public void pushProjection() { + if (projectionStackDepth == MATRIX_STACK_DEPTH) { + throw new RuntimeException(ERROR_PUSHMATRIX_OVERFLOW); + } + projection.get(projectionStack[projectionStackDepth]); + projectionStackDepth++; + } + + + public void popProjection() { + flush(); // The geometry with the old projection matrix needs to be drawn now + + if (projectionStackDepth == 0) { + throw new RuntimeException(ERROR_PUSHMATRIX_UNDERFLOW); + } + projectionStackDepth--; + projection.set(projectionStack[projectionStackDepth]); + updateProjmodelview(); + } + + + public void resetProjection() { + flush(); + projection.reset(); + updateProjmodelview(); + } + + + public void applyProjection(PMatrix3D mat) { + flush(); + projection.apply(mat); + updateProjmodelview(); + } + + + public void applyProjection(float n00, float n01, float n02, float n03, + float n10, float n11, float n12, float n13, + float n20, float n21, float n22, float n23, + float n30, float n31, float n32, float n33) { + flush(); + projection.apply(n00, n01, n02, n03, + n10, n11, n12, n13, + n20, n21, n22, n23, + n30, n31, n32, n33); + updateProjmodelview(); + } + + + public void setProjection(PMatrix3D mat) { + flush(); + projection.set(mat); + updateProjmodelview(); + } + + + // Returns true if the matrix is of the form: + // x, 0, 0, a, + // 0, y, 0, b, + // 0, 0, z, c, + // 0, 0, 0, 1 + protected boolean orthoProjection() { + return zero(projection.m01) && zero(projection.m02) && + zero(projection.m10) && zero(projection.m12) && + zero(projection.m20) && zero(projection.m21) && + zero(projection.m30) && zero(projection.m31) && + zero(projection.m32) && same(projection.m33, 1); + } + + + protected boolean nonOrthoProjection() { + return nonZero(projection.m01) || nonZero(projection.m02) || + nonZero(projection.m10) || nonZero(projection.m12) || + nonZero(projection.m20) || nonZero(projection.m21) || + nonZero(projection.m30) || nonZero(projection.m31) || + nonZero(projection.m32) || diff(projection.m33, 1); + } + + + ////////////////////////////////////////////////////////////// + + // Some float math utilities + + + protected static boolean same(float a, float b) { + return Math.abs(a - b) < PGL.FLOAT_EPS; + } + + + protected static boolean diff(float a, float b) { + return PGL.FLOAT_EPS <= Math.abs(a - b); + } + + + protected static boolean zero(float a) { + return Math.abs(a) < PGL.FLOAT_EPS; + } + + + protected static boolean nonZero(float a) { + return PGL.FLOAT_EPS <= Math.abs(a); + } + + + ////////////////////////////////////////////////////////////// + + // CAMERA + + /** + * Set matrix mode to the camera matrix (instead of the current transformation + * matrix). This means applyMatrix, resetMatrix, etc. will affect the camera. + *

+ * Note that the camera matrix is *not* the perspective matrix, it contains + * the values of the modelview matrix immediatly after the latter was + * initialized with ortho() or camera(), or the modelview matrix as result of + * the operations applied between beginCamera()/endCamera(). + *

+ * beginCamera() specifies that all coordinate transforms until endCamera() + * should be pre-applied in inverse to the camera transform matrix. Note that + * this is only challenging when a user specifies an arbitrary matrix with + * applyMatrix(). Then that matrix will need to be inverted, which may not be + * possible. But take heart, if a user is applying a non-invertible matrix to + * the camera transform, then he is clearly up to no good, and we can wash our + * hands of those bad intentions. + *

+ * begin/endCamera clauses do not automatically reset the camera transform + * matrix. That's because we set up a nice default camera transform in + * setup(), and we expect it to hold through draw(). So we don't reset the + * camera transform matrix at the top of draw(). That means that an + * innocuous-looking clause like + * + *

+   * beginCamera();
+   * translate(0, 0, 10);
+   * endCamera();
+   * 
+ * + * at the top of draw(), will result in a runaway camera that shoots + * infinitely out of the screen over time. In order to prevent this, it is + * necessary to call some function that does a hard reset of the camera + * transform matrix inside of begin/endCamera. Two options are + * + *
+   * camera(); // sets up the nice default camera transform
+   * resetMatrix(); // sets up the identity camera transform
+   * 
+ * + * So to rotate a camera a constant amount, you might try + * + *
+   * beginCamera();
+   * camera();
+   * rotateY(PI / 8);
+   * endCamera();
+   * 
+ */ + @Override + public void beginCamera() { + if (manipulatingCamera) { + throw new RuntimeException("beginCamera() cannot be called again " + + "before endCamera()"); + } else { + manipulatingCamera = true; + } + } + + + /** + * Record the current settings into the camera matrix, and set the matrix mode + * back to the current transformation matrix. + *

+ * Note that this will destroy any settings to scale(), translate(), or + * whatever, because the final camera matrix will be copied (not multiplied) + * into the modelview. + */ + @Override + public void endCamera() { + if (!manipulatingCamera) { + throw new RuntimeException("Cannot call endCamera() " + + "without first calling beginCamera()"); + } + + camera.set(modelview); + cameraInv.set(modelviewInv); + + // all done + manipulatingCamera = false; + } + + + /** + * Set camera to the default settings. + *

+ * Processing camera behavior: + *

+ * Camera behavior can be split into two separate components, camera + * transformation, and projection. The transformation corresponds to the + * physical location, orientation, and scale of the camera. In a physical + * camera metaphor, this is what can manipulated by handling the camera body + * (with the exception of scale, which doesn't really have a physcial analog). + * The projection corresponds to what can be changed by manipulating the lens. + *

+ * We maintain separate matrices to represent the camera transform and + * projection. An important distinction between the two is that the camera + * transform should be invertible, where the projection matrix should not, + * since it serves to map three dimensions to two. It is possible to bake the + * two matrices into a single one just by multiplying them together, but it + * isn't a good idea, since lighting, z-ordering, and z-buffering all demand a + * true camera z coordinate after modelview and camera transforms have been + * applied but before projection. If the camera transform and projection are + * combined there is no way to recover a good camera-space z-coordinate from a + * model coordinate. + *

+ * Fortunately, there are no functions that manipulate both camera + * transformation and projection. + *

+ * camera() sets the camera position, orientation, and center of the scene. It + * replaces the camera transform with a new one. + *

+ * The transformation functions are the same ones used to manipulate the + * modelview matrix (scale, translate, rotate, etc.). But they are bracketed + * with beginCamera(), endCamera() to indicate that they should apply (in + * inverse), to the camera transformation matrix. + */ + @Override + public void camera() { + camera(cameraX, cameraY, cameraZ, cameraX, cameraY, 0, 0, 1, 0); + } + + + /** + * More flexible method for dealing with camera(). + *

+ * The actual call is like gluLookat. Here's the real skinny on what does + * what: + * + *

+   * camera(); or
+   * camera(ex, ey, ez, cx, cy, cz, ux, uy, uz);
+   * 
+ * + * do not need to be called from with beginCamera();/endCamera(); That's + * because they always apply to the camera transformation, and they always + * totally replace it. That means that any coordinate transforms done before + * camera(); in draw() will be wiped out. It also means that camera() always + * operates in untransformed world coordinates. Therefore it is always + * redundant to call resetMatrix(); before camera(); This isn't technically + * true of gluLookat, but it's pretty much how it's used. + *

+ * Now, beginCamera(); and endCamera(); are useful if you want to move the + * camera around using transforms like translate(), etc. They will wipe out + * any coordinate system transforms that occur before them in draw(), but they + * will not automatically wipe out the camera transform. This means that they + * should be at the top of draw(). It also means that the following: + * + *

+   * beginCamera();
+   * rotateY(PI / 8);
+   * endCamera();
+   * 
+ * + * will result in a camera that spins without stopping. If you want to just + * rotate a small constant amount, try this: + * + *
+   * beginCamera();
+   * camera(); // sets up the default view
+   * rotateY(PI / 8);
+   * endCamera();
+   * 
+ * + * That will rotate a little off of the default view. Note that this is + * entirely equivalent to + * + *
+   * camera(); // sets up the default view
+   * beginCamera();
+   * rotateY(PI / 8);
+   * endCamera();
+   * 
+ * + * because camera() doesn't care whether or not it's inside a begin/end + * clause. Basically it's safe to use camera() or camera(ex, ey, ez, cx, cy, + * cz, ux, uy, uz) as naked calls because they do all the matrix resetting + * automatically. + */ + @Override + public void camera(float eyeX, float eyeY, float eyeZ, + float centerX, float centerY, float centerZ, + float upX, float upY, float upZ) { + // Calculating Z vector + float z0 = eyeX - centerX; + float z1 = eyeY - centerY; + float z2 = eyeZ - centerZ; + float mag = PApplet.sqrt(z0 * z0 + z1 * z1 + z2 * z2); + if (nonZero(mag)) { + z0 /= mag; + z1 /= mag; + z2 /= mag; + } + cameraEyeX = eyeX; + cameraEyeY = eyeY; + cameraEyeZ = eyeZ; + + // Calculating Y vector + float y0 = upX; + float y1 = upY; + float y2 = upZ; + + // Computing X vector as Y cross Z + float x0 = y1 * z2 - y2 * z1; + float x1 = -y0 * z2 + y2 * z0; + float x2 = y0 * z1 - y1 * z0; + + // Recompute Y = Z cross X + y0 = z1 * x2 - z2 * x1; + y1 = -z0 * x2 + z2 * x0; + y2 = z0 * x1 - z1 * x0; + + // Cross product gives area of parallelogram, which is < 1.0 for + // non-perpendicular unit-length vectors; so normalize x, y here: + mag = PApplet.sqrt(x0 * x0 + x1 * x1 + x2 * x2); + if (nonZero(mag)) { + x0 /= mag; + x1 /= mag; + x2 /= mag; + } + + mag = PApplet.sqrt(y0 * y0 + y1 * y1 + y2 * y2); + if (nonZero(mag)) { + y0 /= mag; + y1 /= mag; + y2 /= mag; + } + + modelview.set(x0, x1, x2, 0, + y0, y1, y2, 0, + z0, z1, z2, 0, + 0, 0, 0, 1); + + float tx = -eyeX; + float ty = -eyeY; + float tz = -eyeZ; + modelview.translate(tx, ty, tz); + + modelviewInv.set(modelview); + modelviewInv.invert(); + + camera.set(modelview); + cameraInv.set(modelviewInv); + + updateProjmodelview(); + } + + + /** + * Print the current camera matrix. + */ + @Override + public void printCamera() { + camera.print(); + } + + + protected void defaultCamera() { + camera(); + } + + + ////////////////////////////////////////////////////////////// + + // PROJECTION + + + /** + * Calls ortho() with the proper parameters for Processing's standard + * orthographic projection. + */ + @Override + public void ortho() { + ortho(0, width, 0, height, 0, cameraEyeZ * 10); + } + + + /** + * Calls ortho() with the specified size of the viewing volume along + * the X and Z directions. + */ + @Override + public void ortho(float left, float right, + float bottom, float top) { + ortho(left, right, bottom, top, 0, cameraEyeZ * 10); + } + + + /** + * Sets an orthographic projection. + * + */ + @Override + public void ortho(float left, float right, + float bottom, float top, + float near, float far) { + float w = right - left; + float h = top - bottom; + float d = far - near; + + // Applying the camera translation (only on x and y, as near and far + // are given as distances from the viewer) + left -= cameraEyeX; + right -= cameraEyeX; + bottom -= cameraEyeY; + top -= cameraEyeY; + + // Flushing geometry with a different perspective configuration. + flush(); + + float x = +2.0f / w; + float y = +2.0f / h; + float z = -2.0f / d; + + float tx = -(right + left) / w; + float ty = -(top + bottom) / h; + float tz = -(far + near) / d; + + // The minus sign is needed to invert the Y axis. + projection.set(x, 0, 0, tx, + 0, -y, 0, ty, + 0, 0, z, tz, + 0, 0, 0, 1); + + updateProjmodelview(); + } + + + /** + * Calls perspective() with Processing's standard coordinate projection. + *

+ * Projection functions: + *

    + *
  • frustrum() + *
  • ortho() + *
  • perspective() + *
+ * Each of these three functions completely replaces the projection matrix + * with a new one. They can be called inside setup(), and their effects will + * be felt inside draw(). At the top of draw(), the projection matrix is not + * reset. Therefore the last projection function to be called always + * dominates. On resize, the default projection is always established, which + * has perspective. + *

+ * This behavior is pretty much familiar from OpenGL, except where functions + * replace matrices, rather than multiplying against the previous. + *

+ */ + @Override + public void perspective() { + perspective(cameraFOV, cameraAspect, cameraNear, cameraFar); + } + + + /** + * Similar to gluPerspective(). Implementation based on Mesa's glu.c + */ + @Override + public void perspective(float fov, float aspect, float zNear, float zFar) { + float ymax = zNear * (float) Math.tan(fov / 2); + float ymin = -ymax; + float xmin = ymin * aspect; + float xmax = ymax * aspect; + frustum(xmin, xmax, ymin, ymax, zNear, zFar); + } + + + /** + * Same as glFrustum(), except that it wipes out (rather than multiplies + * against) the current perspective matrix. + *

+ * Implementation based on the explanation in the OpenGL blue book. + */ + @Override + public void frustum(float left, float right, float bottom, float top, + float znear, float zfar) { + // Flushing geometry with a different perspective configuration. + flush(); + + float n2 = 2 * znear; + float w = right - left; + float h = top - bottom; + float d = zfar - znear; + + projection.set(n2 / w, 0, (right + left) / w, 0, + 0, -n2 / h, (top + bottom) / h, 0, + 0, 0, -(zfar + znear) / d, -(n2 * zfar) / d, + 0, 0, -1, 0); + + updateProjmodelview(); + } + + + /** + * Print the current projection matrix. + */ + @Override + public void printProjection() { + projection.print(); + } + + + protected void defaultPerspective() { + perspective(); + } + + + ////////////////////////////////////////////////////////////// + + // SCREEN AND MODEL COORDS + + + @Override + public float screenX(float x, float y) { + return screenXImpl(x, y, 0); + } + + + @Override + public float screenY(float x, float y) { + return screenYImpl(x, y, 0); + } + + + @Override + public float screenX(float x, float y, float z) { + return screenXImpl(x, y, z); + } + + + @Override + public float screenY(float x, float y, float z) { + return screenYImpl(x, y, z); + } + + + @Override + public float screenZ(float x, float y, float z) { + return screenZImpl(x, y, z); + } + + + protected float screenXImpl(float x, float y, float z) { + float ax = + modelview.m00*x + modelview.m01*y + modelview.m02*z + modelview.m03; + float ay = + modelview.m10*x + modelview.m11*y + modelview.m12*z + modelview.m13; + float az = + modelview.m20*x + modelview.m21*y + modelview.m22*z + modelview.m23; + float aw = + modelview.m30*x + modelview.m31*y + modelview.m32*z + modelview.m33; + return screenXImpl(ax, ay, az, aw); + } + + + protected float screenXImpl(float x, float y, float z, float w) { + float ox = + projection.m00*x + projection.m01*y + projection.m02*z + projection.m03*w; + float ow = + projection.m30*x + projection.m31*y + projection.m32*z + projection.m33*w; + + if (nonZero(ow)) { + ox /= ow; + } + float sx = width * (1 + ox) / 2.0f; + return sx; + } + + + protected float screenYImpl(float x, float y, float z) { + float ax = + modelview.m00*x + modelview.m01*y + modelview.m02*z + modelview.m03; + float ay = + modelview.m10*x + modelview.m11*y + modelview.m12*z + modelview.m13; + float az = + modelview.m20*x + modelview.m21*y + modelview.m22*z + modelview.m23; + float aw = + modelview.m30*x + modelview.m31*y + modelview.m32*z + modelview.m33; + return screenYImpl(ax, ay, az, aw); + } + + + protected float screenYImpl(float x, float y, float z, float w) { + float oy = + projection.m10*x + projection.m11*y + projection.m12*z + projection.m13*w; + float ow = + projection.m30*x + projection.m31*y + projection.m32*z + projection.m33*w; + + if (nonZero(ow)) { + oy /= ow; + } + float sy = height * (1 + oy) / 2.0f; + // Turning value upside down because of Processing's inverted Y axis. + sy = height - sy; + return sy; + } + + + protected float screenZImpl(float x, float y, float z) { + float ax = + modelview.m00*x + modelview.m01*y + modelview.m02*z + modelview.m03; + float ay = + modelview.m10*x + modelview.m11*y + modelview.m12*z + modelview.m13; + float az = + modelview.m20*x + modelview.m21*y + modelview.m22*z + modelview.m23; + float aw = + modelview.m30*x + modelview.m31*y + modelview.m32*z + modelview.m33; + return screenZImpl(ax, ay, az, aw); + } + + + protected float screenZImpl(float x, float y, float z, float w) { + float oz = + projection.m20*x + projection.m21*y + projection.m22*z + projection.m23*w; + float ow = + projection.m30*x + projection.m31*y + projection.m32*z + projection.m33*w; + + if (nonZero(ow)) { + oz /= ow; + } + float sz = (oz + 1) / 2.0f; + return sz; + } + + + @Override + public float modelX(float x, float y, float z) { + float ax = + modelview.m00*x + modelview.m01*y + modelview.m02*z + modelview.m03; + float ay = + modelview.m10*x + modelview.m11*y + modelview.m12*z + modelview.m13; + float az = + modelview.m20*x + modelview.m21*y + modelview.m22*z + modelview.m23; + float aw = + modelview.m30*x + modelview.m31*y + modelview.m32*z + modelview.m33; + + float ox = + cameraInv.m00*ax + cameraInv.m01*ay + cameraInv.m02*az + cameraInv.m03*aw; + float ow = + cameraInv.m30*ax + cameraInv.m31*ay + cameraInv.m32*az + cameraInv.m33*aw; + + return nonZero(ow) ? ox / ow : ox; + } + + + @Override + public float modelY(float x, float y, float z) { + float ax = + modelview.m00*x + modelview.m01*y + modelview.m02*z + modelview.m03; + float ay = + modelview.m10*x + modelview.m11*y + modelview.m12*z + modelview.m13; + float az = + modelview.m20*x + modelview.m21*y + modelview.m22*z + modelview.m23; + float aw = + modelview.m30*x + modelview.m31*y + modelview.m32*z + modelview.m33; + + float oy = + cameraInv.m10*ax + cameraInv.m11*ay + cameraInv.m12*az + cameraInv.m13*aw; + float ow = + cameraInv.m30*ax + cameraInv.m31*ay + cameraInv.m32*az + cameraInv.m33*aw; + + return nonZero(ow) ? oy / ow : oy; + } + + + @Override + public float modelZ(float x, float y, float z) { + float ax = + modelview.m00*x + modelview.m01*y + modelview.m02*z + modelview.m03; + float ay = + modelview.m10*x + modelview.m11*y + modelview.m12*z + modelview.m13; + float az = + modelview.m20*x + modelview.m21*y + modelview.m22*z + modelview.m23; + float aw = + modelview.m30*x + modelview.m31*y + modelview.m32*z + modelview.m33; + + float oz = + cameraInv.m20*ax + cameraInv.m21*ay + cameraInv.m22*az + cameraInv.m23*aw; + float ow = + cameraInv.m30*ax + cameraInv.m31*ay + cameraInv.m32*az + cameraInv.m33*aw; + + return nonZero(ow) ? oz / ow : oz; + } + + ////////////////////////////////////////////////////////////// + + // STYLES + + @Override + public void popStyle() { + // popStyle() sets ambient to true (because it calls ambient() in style()) + // and so setting the setAmbient flag to true, even if the user didn't call + // ambient, so need to revert to false. + boolean savedSetAmbient = setAmbient; + super.popStyle(); + if (!savedSetAmbient) setAmbient = false; + } + + // public void pushStyle() + // public void popStyle() + // public void style(PStyle) + // public PStyle getStyle() + // public void getStyle(PStyle) + + ////////////////////////////////////////////////////////////// + + // COLOR MODE + + // public void colorMode(int mode) + // public void colorMode(int mode, float max) + // public void colorMode(int mode, float mx, float my, float mz); + // public void colorMode(int mode, float mx, float my, float mz, float ma); + + ////////////////////////////////////////////////////////////// + + // COLOR CALC + + // protected void colorCalc(int rgb) + // protected void colorCalc(int rgb, float alpha) + // protected void colorCalc(float gray) + // protected void colorCalc(float gray, float alpha) + // protected void colorCalc(float x, float y, float z) + // protected void colorCalc(float x, float y, float z, float a) + // protected void colorCalcARGB(int argb, float alpha) + + ////////////////////////////////////////////////////////////// + + // STROKE CAP/JOIN/WEIGHT + + + @Override + public void strokeWeight(float weight) { + this.strokeWeight = weight; + } + + + @Override + public void strokeJoin(int join) { + this.strokeJoin = join; + } + + + @Override + public void strokeCap(int cap) { + this.strokeCap = cap; + } + + + ////////////////////////////////////////////////////////////// + + // FILL COLOR + + + @Override + protected void fillFromCalc() { + super.fillFromCalc(); + + if (!setAmbient) { + // Setting the ambient color from the current fill + // is what the old P3D did and allows to have an + // default ambient color when the user doesn't specify + // it explicitly. + ambientFromCalc(); + // ambientFromCalc sets setAmbient to true, but it hasn't been + // set by the user so put back to false. + setAmbient = false; + } + } + + + ////////////////////////////////////////////////////////////// + + // LIGHTING + + /** + * Sets up an ambient and directional light using OpenGL. API taken from + * PGraphics3D. + * + *

+   * The Lighting Skinny:
+   * The way lighting works is complicated enough that it's worth
+   * producing a document to describe it. Lighting calculations proceed
+   * pretty much exactly as described in the OpenGL red book.
+   * Light-affecting material properties:
+   *   AMBIENT COLOR
+   *   - multiplies by light's ambient component
+   *   - for believability this should match diffuse color
+   *   DIFFUSE COLOR
+   *   - multiplies by light's diffuse component
+   *   SPECULAR COLOR
+   *   - multiplies by light's specular component
+   *   - usually less colored than diffuse/ambient
+   *   SHININESS
+   *   - the concentration of specular effect
+   *   - this should be set pretty high (20-50) to see really
+   *     noticeable specularity
+   *   EMISSIVE COLOR
+   *   - constant additive color effect
+   * Light types:
+   *   AMBIENT
+   *   - one color
+   *   - no specular color
+   *   - no direction
+   *   - may have falloff (constant, linear, and quadratic)
+   *   - may have position (which matters in non-constant falloff case)
+   *   - multiplies by a material's ambient reflection
+   *   DIRECTIONAL
+   *   - has diffuse color
+   *   - has specular color
+   *   - has direction
+   *   - no position
+   *   - no falloff
+   *   - multiplies by a material's diffuse and specular reflections
+   *   POINT
+   *   - has diffuse color
+   *   - has specular color
+   *   - has position
+   *   - no direction
+   *   - may have falloff (constant, linear, and quadratic)
+   *   - multiplies by a material's diffuse and specular reflections
+   *   SPOT
+   *   - has diffuse color
+   *   - has specular color
+   *   - has position
+   *   - has direction
+   *   - has cone angle (set to half the total cone angle)
+   *   - has concentration value
+   *   - may have falloff (constant, linear, and quadratic)
+   *   - multiplies by a material's diffuse and specular reflections
+   * Normal modes:
+   * All of the primitives (rect, box, sphere, etc.) have their normals
+   * set nicely. During beginShape/endShape normals can be set by the user.
+   *   AUTO-NORMAL
+   *   - if no normal is set during the shape, we are in auto-normal mode
+   *   - auto-normal calculates one normal per triangle (face-normal mode)
+   *   SHAPE-NORMAL
+   *   - if one normal is set during the shape, it will be used for
+   *     all vertices
+   *   VERTEX-NORMAL
+   *   - if multiple normals are set, each normal applies to
+   *     subsequent vertices
+   *   - (except for the first one, which applies to previous
+   *     and subsequent vertices)
+   * Efficiency consequences:
+   *   There is a major efficiency consequence of position-dependent
+   *   lighting calculations per vertex. (See below for determining
+   *   whether lighting is vertex position-dependent.) If there is no
+   *   position dependency then the only factors that affect the lighting
+   *   contribution per vertex are its colors and its normal.
+   *   There is a major efficiency win if
+   *   1) lighting is not position dependent
+   *   2) we are in AUTO-NORMAL or SHAPE-NORMAL mode
+   *   because then we can calculate one lighting contribution per shape
+   *   (SHAPE-NORMAL) or per triangle (AUTO-NORMAL) and simply multiply it
+   *   into the vertex colors. The converse is our worst-case performance when
+   *   1) lighting is position dependent
+   *   2) we are in AUTO-NORMAL mode
+   *   because then we must calculate lighting per-face * per-vertex.
+   *   Each vertex has a different lighting contribution per face in
+   *   which it appears. Yuck.
+   * Determining vertex position dependency:
+   *   If any of the following factors are TRUE then lighting is
+   *   vertex position dependent:
+   *   1) Any lights uses non-constant falloff
+   *   2) There are any point or spot lights
+   *   3) There is a light with specular color AND there is a
+   *      material with specular color
+   * So worth noting is that default lighting (a no-falloff ambient
+   * and a directional without specularity) is not position-dependent.
+   * We should capitalize.
+   * Simon Greenwold, April 2005
+   * 
+ */ + @Override + public void lights() { + enableLighting(); + + // reset number of lights + lightCount = 0; + + // need to make sure colorMode is RGB 255 here + int colorModeSaved = colorMode; + colorMode = RGB; + + lightFalloff(1, 0, 0); + lightSpecular(0, 0, 0); + + ambientLight(colorModeX * 0.5f, colorModeY * 0.5f, colorModeZ * 0.5f); + directionalLight(colorModeX * 0.5f, colorModeY * 0.5f, colorModeZ * 0.5f, + 0, 0, -1); + + colorMode = colorModeSaved; + } + + + /** + * Disables lighting. + */ + @Override + public void noLights() { + disableLighting(); + lightCount = 0; + } + + + /** + * Add an ambient light based on the current color mode. + */ + @Override + public void ambientLight(float r, float g, float b) { + ambientLight(r, g, b, 0, 0, 0); + } + + + /** + * Add an ambient light based on the current color mode. This version includes + * an (x, y, z) position for situations where the falloff distance is used. + */ + @Override + public void ambientLight(float r, float g, float b, + float x, float y, float z) { + enableLighting(); + if (lightCount == PGL.MAX_LIGHTS) { + throw new RuntimeException("can only create " + PGL.MAX_LIGHTS + + " lights"); + } + + lightType[lightCount] = AMBIENT; + + lightPosition(lightCount, x, y, z, false); + lightNormal(lightCount, 0, 0, 0); + + lightAmbient(lightCount, r, g, b); + noLightDiffuse(lightCount); + noLightSpecular(lightCount); + noLightSpot(lightCount); + lightFalloff(lightCount, currentLightFalloffConstant, + currentLightFalloffLinear, + currentLightFalloffQuadratic); + + lightCount++; + } + + + @Override + public void directionalLight(float r, float g, float b, + float dx, float dy, float dz) { + enableLighting(); + if (lightCount == PGL.MAX_LIGHTS) { + throw new RuntimeException("can only create " + PGL.MAX_LIGHTS + + " lights"); + } + + lightType[lightCount] = DIRECTIONAL; + + lightPosition(lightCount, 0, 0, 0, true); + lightNormal(lightCount, dx, dy, dz); + + noLightAmbient(lightCount); + lightDiffuse(lightCount, r, g, b); + lightSpecular(lightCount, currentLightSpecular[0], + currentLightSpecular[1], + currentLightSpecular[2]); + noLightSpot(lightCount); + noLightFalloff(lightCount); + + lightCount++; + } + + + @Override + public void pointLight(float r, float g, float b, + float x, float y, float z) { + enableLighting(); + if (lightCount == PGL.MAX_LIGHTS) { + throw new RuntimeException("can only create " + PGL.MAX_LIGHTS + + " lights"); + } + + lightType[lightCount] = POINT; + + lightPosition(lightCount, x, y, z, false); + lightNormal(lightCount, 0, 0, 0); + + noLightAmbient(lightCount); + lightDiffuse(lightCount, r, g, b); + lightSpecular(lightCount, currentLightSpecular[0], + currentLightSpecular[1], + currentLightSpecular[2]); + noLightSpot(lightCount); + lightFalloff(lightCount, currentLightFalloffConstant, + currentLightFalloffLinear, + currentLightFalloffQuadratic); + + lightCount++; + } + + + @Override + public void spotLight(float r, float g, float b, + float x, float y, float z, + float dx, float dy, float dz, + float angle, float concentration) { + enableLighting(); + if (lightCount == PGL.MAX_LIGHTS) { + throw new RuntimeException("can only create " + PGL.MAX_LIGHTS + + " lights"); + } + + lightType[lightCount] = SPOT; + + lightPosition(lightCount, x, y, z, false); + lightNormal(lightCount, dx, dy, dz); + + noLightAmbient(lightCount); + lightDiffuse(lightCount, r, g, b); + lightSpecular(lightCount, currentLightSpecular[0], + currentLightSpecular[1], + currentLightSpecular[2]); + lightSpot(lightCount, angle, concentration); + lightFalloff(lightCount, currentLightFalloffConstant, + currentLightFalloffLinear, + currentLightFalloffQuadratic); + + lightCount++; + } + + + /** + * Set the light falloff rates for the last light that was created. Default is + * lightFalloff(1, 0, 0). + */ + @Override + public void lightFalloff(float constant, float linear, float quadratic) { + currentLightFalloffConstant = constant; + currentLightFalloffLinear = linear; + currentLightFalloffQuadratic = quadratic; + } + + + /** + * Set the specular color of the last light created. + */ + @Override + public void lightSpecular(float x, float y, float z) { + colorCalc(x, y, z); + currentLightSpecular[0] = calcR; + currentLightSpecular[1] = calcG; + currentLightSpecular[2] = calcB; + } + + + protected void enableLighting() { + if (!lights) { + flush(); // Flushing non-lit geometry. + lights = true; + } + } + + + protected void disableLighting() { + if (lights) { + flush(); // Flushing lit geometry. + lights = false; + } + } + + + protected void lightPosition(int num, float x, float y, float z, + boolean dir) { + lightPosition[4 * num + 0] = + x*modelview.m00 + y*modelview.m01 + z*modelview.m02 + modelview.m03; + lightPosition[4 * num + 1] = + x*modelview.m10 + y*modelview.m11 + z*modelview.m12 + modelview.m13; + lightPosition[4 * num + 2] = + x*modelview.m20 + y*modelview.m21 + z*modelview.m22 + modelview.m23; + + // Used to inicate if the light is directional or not. + lightPosition[4 * num + 3] = dir ? 1: 0; + } + + + protected void lightNormal(int num, float dx, float dy, float dz) { + // Applying normal matrix to the light direction vector, which is the + // transpose of the inverse of the modelview. + float nx = + dx*modelviewInv.m00 + dy*modelviewInv.m10 + dz*modelviewInv.m20; + float ny = + dx*modelviewInv.m01 + dy*modelviewInv.m11 + dz*modelviewInv.m21; + float nz = + dx*modelviewInv.m02 + dy*modelviewInv.m12 + dz*modelviewInv.m22; + + float d = PApplet.dist(0, 0, 0, nx, ny, nz); + if (0 < d) { + float invn = 1.0f / d; + lightNormal[3 * num + 0] = invn * nx; + lightNormal[3 * num + 1] = invn * ny; + lightNormal[3 * num + 2] = invn * nz; + } else { + lightNormal[3 * num + 0] = 0; + lightNormal[3 * num + 1] = 0; + lightNormal[3 * num + 2] = 0; + } + } + + + protected void lightAmbient(int num, float r, float g, float b) { + colorCalc(r, g, b); + lightAmbient[3 * num + 0] = calcR; + lightAmbient[3 * num + 1] = calcG; + lightAmbient[3 * num + 2] = calcB; + } + + + protected void noLightAmbient(int num) { + lightAmbient[3 * num + 0] = 0; + lightAmbient[3 * num + 1] = 0; + lightAmbient[3 * num + 2] = 0; + } + + + protected void lightDiffuse(int num, float r, float g, float b) { + colorCalc(r, g, b); + lightDiffuse[3 * num + 0] = calcR; + lightDiffuse[3 * num + 1] = calcG; + lightDiffuse[3 * num + 2] = calcB; + } + + + protected void noLightDiffuse(int num) { + lightDiffuse[3 * num + 0] = 0; + lightDiffuse[3 * num + 1] = 0; + lightDiffuse[3 * num + 2] = 0; + } + + + protected void lightSpecular(int num, float r, float g, float b) { + lightSpecular[3 * num + 0] = r; + lightSpecular[3 * num + 1] = g; + lightSpecular[3 * num + 2] = b; + } + + + protected void noLightSpecular(int num) { + lightSpecular[3 * num + 0] = 0; + lightSpecular[3 * num + 1] = 0; + lightSpecular[3 * num + 2] = 0; + } + + + protected void lightFalloff(int num, float c0, float c1, float c2) { + lightFalloffCoefficients[3 * num + 0] = c0; + lightFalloffCoefficients[3 * num + 1] = c1; + lightFalloffCoefficients[3 * num + 2] = c2; + } + + + protected void noLightFalloff(int num) { + lightFalloffCoefficients[3 * num + 0] = 1; + lightFalloffCoefficients[3 * num + 1] = 0; + lightFalloffCoefficients[3 * num + 2] = 0; + } + + + protected void lightSpot(int num, float angle, float exponent) { + lightSpotParameters[2 * num + 0] = Math.max(0, PApplet.cos(angle)); + lightSpotParameters[2 * num + 1] = exponent; + } + + + protected void noLightSpot(int num) { + lightSpotParameters[2 * num + 0] = 0; + lightSpotParameters[2 * num + 1] = 0; + } + + + ////////////////////////////////////////////////////////////// + + // BACKGROUND + + + @Override + protected void backgroundImpl(PImage image) { + backgroundImpl(); + set(0, 0, image); + if (0 < parent.frameCount) { + clearColorBuffer = true; + } + // Setting the background as opaque. If this an offscreen surface, the + // alpha channel will be set to 1 in endOffscreenDraw(), even if + // blending operations during draw create translucent areas in the + // color buffer. + backgroundA = 1; + } + + + @Override + protected void backgroundImpl() { + flush(); + + if (!hints[DISABLE_DEPTH_MASK]) { + pgl.clearDepth(1); + pgl.clear(PGL.DEPTH_BUFFER_BIT); + } + + pgl.clearColor(backgroundR, backgroundG, backgroundB, backgroundA); + pgl.clear(PGL.COLOR_BUFFER_BIT); + if (0 < parent.frameCount) { + clearColorBuffer = true; + } + } + + + ////////////////////////////////////////////////////////////// + + // COLOR MODE + + // colorMode() is inherited from PGraphics. + + + ////////////////////////////////////////////////////////////// + + // COLOR METHODS + + // public final int color(int gray) + // public final int color(int gray, int alpha) + // public final int color(int rgb, float alpha) + // public final int color(int x, int y, int z) + + // public final float alpha(int what) + // public final float red(int what) + // public final float green(int what) + // public final float blue(int what) + // public final float hue(int what) + // public final float saturation(int what) + // public final float brightness(int what) + + // public int lerpColor(int c1, int c2, float amt) + // static public int lerpColor(int c1, int c2, float amt, int mode) + + ////////////////////////////////////////////////////////////// + + // BEGINRAW/ENDRAW + + // beginRaw, endRaw() both inherited. + + ////////////////////////////////////////////////////////////// + + // WARNINGS and EXCEPTIONS + + // showWarning() and showException() available from PGraphics. + + /** + * Report on anything from glError(). + * Don't use this inside glBegin/glEnd otherwise it'll + * throw an GL_INVALID_OPERATION error. + */ + protected void report(String where) { + if (!hints[DISABLE_OPENGL_ERRORS]) { + int err = pgl.getError(); + if (err != 0) { + String errString = pgl.errorString(err); + String msg = "OpenGL error " + err + " at " + where + ": " + errString; + PGraphics.showWarning(msg); + } + } + } + + + + ////////////////////////////////////////////////////////////// + + // RENDERER SUPPORT QUERIES + + // public boolean displayable() + + + @Override + public boolean isGL() { + return true; + } + + + ////////////////////////////////////////////////////////////// + + // LOAD/UPDATE PIXELS + + + // Initializes the pixels array, copying the current contents of the + // color buffer into it. + @Override + public void loadPixels() { + if (primarySurface && sized) { + // Something wrong going on with threading, sized can never be true if + // all the steps in a resize happen inside the Animation thread. + return; + } + + boolean needEndDraw = false; + if (!drawing) { + beginDraw(); + needEndDraw = true; + } + + if (!arePixelsUpToDate) { + // Draws any remaining geometry in case the user is still not + // setting/getting new pixels. + flush(); + } + + allocatePixels(); + + if (!arePixelsUpToDate) { + readPixels(); + } + + // Pixels are now up-to-date, set the flag. + arePixelsUpToDate = true; + + if (needEndDraw) { + endDraw(); + } + } + + + protected void allocatePixels() { + if ((pixels == null) || (pixels.length != width * height)) { + pixels = new int[width * height]; + pixelBuffer = PGL.allocateIntBuffer(pixels); + } + } + + + protected void saveSurfaceToPixels() { + allocatePixels(); + readPixels(); + } + + + protected void restoreSurfaceFromPixels() { + drawPixels(0, 0, width, height); + } + + + protected void readPixels() { + beginPixelsOp(OP_READ); + try { + // The readPixelsImpl() call in inside a try/catch block because it appears + // that (only sometimes) JOGL will run beginDraw/endDraw on the EDT + // thread instead of the Animation thread right after a resize. Because + // of this the width and height might have a different size than the + // one of the pixels arrays. + pgl.readPixelsImpl(0, 0, width, height, PGL.RGBA, PGL.UNSIGNED_BYTE, + pixelBuffer); + } catch (IndexOutOfBoundsException e) { + // Silently catch the exception. + } + endPixelsOp(); + try { + // Idem... + PGL.getIntArray(pixelBuffer, pixels); + PGL.nativeToJavaARGB(pixels, width, height); + } catch (ArrayIndexOutOfBoundsException e) { + } + } + + + protected void drawPixels(int x, int y, int w, int h) { + int len = w * h; + if (nativePixels == null || nativePixels.length < len) { + nativePixels = new int[len]; + nativePixelBuffer = PGL.allocateIntBuffer(nativePixels); + } + + try { + if (0 < x || 0 < y || w < width || h < height) { + // The pixels to be copied to the texture need to be consecutive, and + // they are not in the pixels array, so putting each row one after + // another in nativePixels. + int offset0 = y * width + x; + int offset1 = 0; + + for (int yc = y; yc < y + h; yc++) { + System.arraycopy(pixels, offset0, nativePixels, offset1, w); + offset0 += width; + offset1 += w; + } + } else { + PApplet.arrayCopy(pixels, 0, nativePixels, 0, len); + } + PGL.javaToNativeARGB(nativePixels, w, h); + } catch (ArrayIndexOutOfBoundsException e) { + } + PGL.putIntArray(nativePixelBuffer, nativePixels); + // Copying pixel buffer to screen texture... + if (primarySurface && !pgl.isFBOBacked()) { + // First making sure that the screen texture is valid. Only in the case + // of non-FBO-backed primary surface we might need to create the texture. + loadTextureImpl(POINT, false); + } + + boolean needToDrawTex = primarySurface && (!pgl.isFBOBacked() || + (pgl.isFBOBacked() && pgl.isMultisampled())) || + offscreenMultisample; + if (needToDrawTex) { + // The texture to screen needs to be drawn only if we are on the primary + // surface w/out FBO-layer, or with FBO-layer and multisampling. Or, we + // are doing multisampled offscreen. Why? Because in the case of + // non-multisampled FBO, texture is actually the color buffer used by the + // color FBO, so with the copy operation we should be done updating the + // (off)screen buffer. + // First, copy the pixels to the texture. We don't need to invert the + // pixel copy because the texture will be drawn inverted. + int tw = PApplet.min(texture.glWidth - x, w); + int th = PApplet.min(texture.glHeight - y, h); + pgl.copyToTexture(texture.glTarget, texture.glFormat, texture.glName, + x, y, tw, th, nativePixelBuffer); + beginPixelsOp(OP_WRITE); + drawTexture(x, y, w, h); + endPixelsOp(); + } else { + // We only need to copy the pixels to the back texture where we are + // currently drawing to. Because the texture is invertex along Y, we + // need to reflect that in the vertical arguments. + pgl.copyToTexture(texture.glTarget, texture.glFormat, texture.glName, + x, height - (y + h), w, h, nativePixelBuffer); + } + } + + + ////////////////////////////////////////////////////////////// + + // GET/SET PIXELS + + + @Override + public int get(int x, int y) { + loadPixels(); + return super.get(x, y); + } + + + @Override + protected void getImpl(int sourceX, int sourceY, + int sourceWidth, int sourceHeight, + PImage target, int targetX, int targetY) { + loadPixels(); + super.getImpl(sourceX, sourceY, sourceWidth, sourceHeight, + target, targetX, targetY); + } + + + @Override + public void set(int x, int y, int argb) { + loadPixels(); + super.set(x, y, argb); + } + + + @Override + protected void setImpl(PImage sourceImage, + int sourceX, int sourceY, + int sourceWidth, int sourceHeight, + int targetX, int targetY) { + loadPixels(); + super.setImpl(sourceImage, sourceX, sourceY, sourceWidth, sourceHeight, + targetX, targetY); + // do we need this? + // see https://github.com/processing/processing/issues/2125 +// if (sourceImage.format == RGB) { +// int targetOffset = targetY * width + targetX; +// for (int y = sourceY; y < sourceY + sourceHeight; y++) { +// for (int x = targetOffset; x < targetOffset + sourceWidth; x++) { +// pixels[x] |= 0xff000000; +// } +// targetOffset += width; +// } +// } + } + + + ////////////////////////////////////////////////////////////// + + // SAVE + + + @Override + public boolean save(String filename) { + + // Act as an opaque surface for the purposes of saving. + if (primarySurface) { + int prevFormat = format; + format = RGB; + boolean result = super.save(filename); + format = prevFormat; + return result; + } + + return super.save(filename); + } + + + ////////////////////////////////////////////////////////////// + + // LOAD/UPDATE TEXTURE + + + // Loads the current contents of the renderer's drawing surface into the + // its texture. + public void loadTexture() { + boolean needEndDraw = false; + if (!drawing) { + beginDraw(); + needEndDraw = true; + } + + flush(); // To make sure the color buffer is updated. + + if (primarySurface) { + if (pgl.isFBOBacked()) { + // In the case of MSAA, this is needed so the back buffer is in sync + // with the rendering. + pgl.syncBackTexture(); + } else { + loadTextureImpl(Texture.POINT, false); + + // Here we go the slow route: we first copy the contents of the color + // buffer into a pixels array (but we keep it in native format) and + // then copy this array into the texture. + if (nativePixels == null || nativePixels.length < width * height) { + nativePixels = new int[width * height]; + nativePixelBuffer = PGL.allocateIntBuffer(nativePixels); + } + + beginPixelsOp(OP_READ); + try { + // See comments in readPixels() for the reason for this try/catch. + pgl.readPixelsImpl(0, 0, width, height, PGL.RGBA, PGL.UNSIGNED_BYTE, + nativePixelBuffer); + } catch (IndexOutOfBoundsException e) { + } + endPixelsOp(); + + texture.setNative(nativePixelBuffer, 0, 0, width, height); + } + } else if (offscreenMultisample) { + // We need to copy the contents of the multisampled buffer to the color + // buffer, so the later is up-to-date with the last drawing. + multisampleFramebuffer.copyColor(offscreenFramebuffer); + } + + if (needEndDraw) { + endDraw(); + } + } + + + // Just marks the whole texture as updated + public void updateTexture() { + texture.updateTexels(); + } + + + // Marks the specified rectanglular subregion in the texture as + // updated. + public void updateTexture(int x, int y, int w, int h) { + texture.updateTexels(x, y, w, h); + } + + + // Draws wherever it is in the screen texture right now to the display. + public void updateDisplay() { + flush(); + beginPixelsOp(OP_WRITE); + drawTexture(); + endPixelsOp(); + } + + + protected void loadTextureImpl(int sampling, boolean mipmap) { + if (width == 0 || height == 0) return; + if (texture == null || texture.contextIsOutdated()) { + Texture.Parameters params = new Texture.Parameters(ARGB, + sampling, mipmap); + texture = new Texture(this, width, height, params); + texture.invertedY(true); + texture.colorBuffer(true); + setCache(this, texture); + } + } + + + protected void createPTexture() { + ptexture = new Texture(this, width, height, texture.getParameters()); + ptexture.invertedY(true); + ptexture.colorBuffer(true); + } + + + protected void swapOffscreenTextures() { + if (ptexture != null) { + int temp = texture.glName; + texture.glName = ptexture.glName; + ptexture.glName = temp; + offscreenFramebuffer.setColorBuffer(texture); + } + } + + + protected void drawTexture() { + // No blend so the texure replaces wherever is on the screen, + // irrespective of the alpha + pgl.disable(PGL.BLEND); + pgl.drawTexture(texture.glTarget, texture.glName, + texture.glWidth, texture.glHeight, + 0, 0, width, height); + pgl.enable(PGL.BLEND); + } + + + protected void drawTexture(int x, int y, int w, int h) { + // Processing Y axis is inverted with respect to OpenGL, so we need to + // invert the y coordinates of the screen rectangle. + pgl.disable(PGL.BLEND); + pgl.drawTexture(texture.glTarget, texture.glName, texture.glWidth, texture.glHeight, + 0, 0, width, height, + x, y, x + w, y + h, + x, height - (y + h), x + w, height - y); + pgl.enable(PGL.BLEND); + } + + + protected void drawPTexture() { + if (ptexture != null) { + // No blend so the texure replaces wherever is on the screen, + // irrespective of the alpha + pgl.disable(PGL.BLEND); + pgl.drawTexture(ptexture.glTarget, ptexture.glName, + ptexture.glWidth, ptexture.glHeight, + 0, 0, width, height); + pgl.enable(PGL.BLEND); + } + } + + + ////////////////////////////////////////////////////////////// + + // MASK + + +// @Override +// public void mask(int alpha[]) { +// PImage temp = get(); +// temp.mask(alpha); +// set(0, 0, temp); +// } + + + @Override + public void mask(PImage alpha) { + if (alpha.width != width || alpha.height != height) { + throw new RuntimeException("The PImage used with mask() must be " + + "the same size as the applet."); + } + + PGraphicsOpenGL ppg = getPrimaryPG(); + if (ppg.maskShader == null) { + ppg.maskShader = new PShader(parent, defTextureShaderVertURL, + maskShaderFragURL); + } + ppg.maskShader.set("mask", alpha); + filter(ppg.maskShader); + } + + + + ////////////////////////////////////////////////////////////// + + // FILTER + + + /** + * This is really inefficient and not a good idea in OpenGL. Use get() and + * set() with a smaller image area, or call the filter on an image instead, + * and then draw that. + */ + @Override + public void filter(int kind) { + PImage temp = get(); + temp.filter(kind); + set(0, 0, temp); + } + + + /** + * This is really inefficient and not a good idea in OpenGL. Use get() and + * set() with a smaller image area, or call the filter on an image instead, + * and then draw that. + */ + @Override + public void filter(int kind, float param) { + PImage temp = get(); + temp.filter(kind, param); + set(0, 0, temp); + } + + + @Override + public void filter(PShader shader) { + if (!shader.isPolyShader()) { + PGraphics.showWarning(INVALID_FILTER_SHADER_ERROR); + return; + } + + boolean needEndDraw = false; + if (primarySurface) pgl.requestFBOLayer(); + else if (!drawing) { + beginDraw(); + needEndDraw = true; + } + loadTexture(); + + if (filterTexture == null || filterTexture.contextIsOutdated()) { + filterTexture = new Texture(this, texture.width, texture.height, + texture.getParameters()); + filterTexture.invertedY(true); + filterImage = wrapTexture(filterTexture); + } + filterTexture.set(texture); + + // Disable writing to the depth buffer, so that after applying the filter we + // can still use the depth information to keep adding geometry to the scene. + pgl.depthMask(false); + // Also disabling depth testing so the texture is drawn on top of everything + // that has been drawn before. + pgl.disable(PGL.DEPTH_TEST); + + // Drawing a textured quad in 2D, covering the entire screen, + // with the filter shader applied to it: + begin2D(); + + // Changing light configuration and shader after begin2D() + // because it calls flush(). + boolean prevLights = lights; + lights = false; + int prevTextureMode = textureMode; + textureMode = NORMAL; + boolean prevStroke = stroke; + stroke = false; + int prevBlendMode = blendMode; + blendMode(REPLACE); + PShader prevShader = polyShader; + polyShader = shader; + + beginShape(QUADS); + texture(filterImage); + vertex(0, 0, 0, 0); + vertex(width, 0, 1, 0); + vertex(width, height, 1, 1); + vertex(0, height, 0, 1); + endShape(); + end2D(); + + // Restoring previous configuration. + polyShader = prevShader; + stroke = prevStroke; + lights = prevLights; + textureMode = prevTextureMode; + blendMode(prevBlendMode); + + if (!hints[DISABLE_DEPTH_TEST]) { + pgl.enable(PGL.DEPTH_TEST); + } + if (!hints[DISABLE_DEPTH_MASK]) { + pgl.depthMask(true); + } + + if (needEndDraw) { + endDraw(); + } + } + + + ////////////////////////////////////////////////////////////// + + // COPY + + + @Override + public void copy(int sx, int sy, int sw, int sh, + int dx, int dy, int dw, int dh) { + if (primarySurface) pgl.requestFBOLayer(); + loadTexture(); + if (filterTexture == null || filterTexture.contextIsOutdated()) { + filterTexture = new Texture(this, texture.width, texture.height, + texture.getParameters()); + filterTexture.invertedY(true); + filterImage = wrapTexture(filterTexture); + } + filterTexture.put(texture, sx, height - (sy + sh), sw, height - sy); + copy(filterImage, sx, sy, sw, sh, dx, dy, dw, dh); + } + + + @Override + public void copy(PImage src, + int sx, int sy, int sw, int sh, + int dx, int dy, int dw, int dh) { + boolean needEndDraw = false; + if (!drawing) { + beginDraw(); + needEndDraw = true; + } + + flush(); // make sure that the screen contents are up to date. + + Texture tex = getTexture(src); + boolean invX = tex.invertedX(); + boolean invY = tex.invertedY(); + int scrX0, scrX1; + int scrY0, scrY1; + if (invX) { + scrX0 = dx + dw; + scrX1 = dx; + } else { + scrX0 = dx; + scrX1 = dx + dw; + } + + int texX0 = sx; + int texX1 = sx + sw; + int texY0, texY1; + if (invY) { + scrY0 = height - (dy + dh); + scrY1 = height - dy; + texY0 = tex.height - (sy + sh); + texY1 = tex.height - sy; + } else { + // Because drawTexture uses bottom-to-top orientation of Y axis. + scrY0 = height - dy; + scrY1 = height - (dy + dh); + texY0 = sy; + texY1 = sy + sh; + } + + pgl.drawTexture(tex.glTarget, tex.glName, tex.glWidth, tex.glHeight, + 0, 0, width, height, + texX0, texY0, texX1, texY1, + scrX0, scrY0, scrX1, scrY1); + + + if (needEndDraw) { + endDraw(); + } + } + + + ////////////////////////////////////////////////////////////// + + // BLEND + + + /** + * Allows to set custom blend modes for the entire scene, using openGL. + * Reference article about blending modes: + * http://www.pegtop.net/delphi/articles/blendmodes/ + * DIFFERENCE, HARD_LIGHT, SOFT_LIGHT, OVERLAY, DODGE, BURN modes cannot be + * implemented in fixed-function pipeline because they require + * conditional blending and non-linear blending equations. + */ + @Override + protected void blendModeImpl() { + if (blendMode != lastBlendMode) { + // Flush any geometry that uses a different blending mode. + flush(); + } + + pgl.enable(PGL.BLEND); + + if (blendMode == REPLACE) { + if (blendEqSupported) { + pgl.blendEquation(PGL.FUNC_ADD); + } + pgl.blendFunc(PGL.ONE, PGL.ZERO); + + } else if (blendMode == BLEND) { + if (blendEqSupported) { + pgl.blendEquationSeparate(PGL.FUNC_ADD, + PGL.FUNC_ADD); + } + pgl.blendFuncSeparate(PGL.SRC_ALPHA, PGL.ONE_MINUS_SRC_ALPHA, + PGL.ONE, PGL.ONE); + + } else if (blendMode == ADD) { + if (blendEqSupported) { + pgl.blendEquationSeparate(PGL.FUNC_ADD, + PGL.FUNC_ADD); + } + pgl.blendFuncSeparate(PGL.SRC_ALPHA, PGL.ONE, + PGL.ONE, PGL.ONE); + + } else if (blendMode == SUBTRACT) { + if (blendEqSupported) { + pgl.blendEquationSeparate(PGL.FUNC_REVERSE_SUBTRACT, + PGL.FUNC_ADD); + pgl.blendFuncSeparate(PGL.SRC_ALPHA, PGL.ONE, + PGL.ONE, PGL.ONE); + } else { + PGraphics.showWarning(BLEND_DRIVER_ERROR, "SUBTRACT"); + } + + } else if (blendMode == LIGHTEST) { + if (blendEqSupported) { + pgl.blendEquationSeparate(PGL.FUNC_MAX, + PGL.FUNC_ADD); + pgl.blendFuncSeparate(PGL.ONE, PGL.ONE, + PGL.ONE, PGL.ONE); + } else { + PGraphics.showWarning(BLEND_DRIVER_ERROR, "LIGHTEST"); + } + + } else if (blendMode == DARKEST) { + if (blendEqSupported) { + pgl.blendEquationSeparate(PGL.FUNC_MIN, + PGL.FUNC_ADD); + pgl.blendFuncSeparate(PGL.ONE, PGL.ONE, + PGL.ONE, PGL.ONE); + } else { + PGraphics.showWarning(BLEND_DRIVER_ERROR, "DARKEST"); + } + + } else if (blendMode == EXCLUSION) { + if (blendEqSupported) { + pgl.blendEquationSeparate(PGL.FUNC_ADD, + PGL.FUNC_ADD); + } + pgl.blendFuncSeparate(PGL.ONE_MINUS_DST_COLOR, PGL.ONE_MINUS_SRC_COLOR, + PGL.ONE, PGL.ONE); + + } else if (blendMode == MULTIPLY) { + if (blendEqSupported) { + pgl.blendEquationSeparate(PGL.FUNC_ADD, + PGL.FUNC_ADD); + } + pgl.blendFuncSeparate(PGL.ZERO, PGL.SRC_COLOR, + PGL.ONE, PGL.ONE); + + } else if (blendMode == SCREEN) { + if (blendEqSupported) { + pgl.blendEquationSeparate(PGL.FUNC_ADD, + PGL.FUNC_ADD); + } + pgl.blendFuncSeparate(PGL.ONE_MINUS_DST_COLOR, PGL.ONE, + PGL.ONE, PGL.ONE); + + } else if (blendMode == DIFFERENCE) { + PGraphics.showWarning(BLEND_RENDERER_ERROR, "DIFFERENCE"); + + } else if (blendMode == OVERLAY) { + PGraphics.showWarning(BLEND_RENDERER_ERROR, "OVERLAY"); + + } else if (blendMode == HARD_LIGHT) { + PGraphics.showWarning(BLEND_RENDERER_ERROR, "HARD_LIGHT"); + + } else if (blendMode == SOFT_LIGHT) { + PGraphics.showWarning(BLEND_RENDERER_ERROR, "SOFT_LIGHT"); + + } else if (blendMode == DODGE) { + PGraphics.showWarning(BLEND_RENDERER_ERROR, "DODGE"); + + } else if (blendMode == BURN) { + PGraphics.showWarning(BLEND_RENDERER_ERROR, "BURN"); + } + lastBlendMode = blendMode; + } + + + ////////////////////////////////////////////////////////////// + + // SAVE + + // public void save(String filename) // PImage calls loadPixels() + + + ////////////////////////////////////////////////////////////// + + // TEXTURE UTILS + + + /** + * Not an approved function, this will change or be removed in the future. + * This utility method returns the texture associated to the renderer's. + * drawing surface, making sure is updated to reflect the current contents + * off the screen (or offscreen drawing surface). + */ + public Texture getTexture() { + return getTexture(true); + } + + + /** + * Not an approved function either, don't use it. + */ + public Texture getTexture(boolean load) { + if (load) loadTexture(); + return texture; + } + + + /** + * Not an approved function, this will change or be removed in the future. + * This utility method returns the texture associated to the image. + * creating and/or updating it if needed. + * + * @param img the image to have a texture metadata associated to it + */ + public Texture getTexture(PImage img) { + Texture tex = (Texture)initCache(img); + if (tex == null) return null; + + if (img.isModified() || img.isLoaded()) { + if (img.width != tex.width || img.height != tex.height) { + tex.init(img.width, img.height); + } + updateTexture(img, tex); + } + + if (tex.hasBuffers()) { + tex.bufferUpdate(); + } + + checkTexture(tex); + + return tex; + } + + + /** + * Not an approved function, test its use in libraries to grab the FB objects + * for offscreen PGraphics. + */ + public FrameBuffer getFrameBuffer() { + return getFrameBuffer(false); + } + + + public FrameBuffer getFrameBuffer(boolean multi) { + if (multi) { + return multisampleFramebuffer; + } else { + return offscreenFramebuffer; + } + } + + + protected Object initCache(PImage img) { + if (!checkGLThread()) { + return null; + } + + Texture tex = (Texture)getCache(img); + if (tex == null || tex.contextIsOutdated()) { + tex = addTexture(img); + if (tex != null) { + img.loadPixels(); + tex.set(img.pixels, img.format); + img.setLoaded(false); + } + } + return tex; + } + + + protected void bindFrontTexture() { + if (primarySurface) { + pgl.bindFrontTexture(); + } else { + if (ptexture == null) createPTexture(); + ptexture.bind(); + } + } + + + protected void unbindFrontTexture() { + if (primarySurface) { + pgl.unbindFrontTexture(); + } else { + ptexture.unbind(); + } + } + + + /** + * This utility method creates a texture for the provided image, and adds it + * to the metadata cache of the image. + * @param img the image to have a texture metadata associated to it + */ + protected Texture addTexture(PImage img) { + Texture.Parameters params = + new Texture.Parameters(ARGB, textureSampling, + getHint(ENABLE_TEXTURE_MIPMAPS), textureWrap); + return addTexture(img, params); + } + + + protected Texture addTexture(PImage img, Texture.Parameters params) { + if (img.width == 0 || img.height == 0) { + // Cannot add textures of size 0 + return null; + } + if (img.parent == null) { + img.parent = parent; + } + Texture tex = new Texture(this, img.width, img.height, params); + setCache(img, tex); + return tex; + } + + + protected void checkTexture(Texture tex) { + if (!tex.colorBuffer() && + tex.usingMipmaps == hints[DISABLE_TEXTURE_MIPMAPS]) { + if (hints[DISABLE_TEXTURE_MIPMAPS]) { + tex.usingMipmaps(false, textureSampling); + } else { + tex.usingMipmaps(true, textureSampling); + } + } + + if ((tex.usingRepeat && textureWrap == CLAMP) || + (!tex.usingRepeat && textureWrap == REPEAT)) { + if (textureWrap == CLAMP) { + tex.usingRepeat(false); + } else { + tex.usingRepeat(true); + } + } + } + + + protected PImage wrapTexture(Texture tex) { + // We don't use the PImage(int width, int height, int mode) constructor to + // avoid initializing the pixels array. + PImage img = new PImage(); + img.parent = parent; + img.width = tex.width; + img.height = tex.height; + img.format = ARGB; + setCache(img, tex); + return img; + } + + + protected void updateTexture(PImage img, Texture tex) { + if (tex != null) { + if (img.isModified()) { + int x = img.getModifiedX1(); + int y = img.getModifiedY1(); + int w = img.getModifiedX2() - x; + int h = img.getModifiedY2() - y; + tex.set(img.pixels, x, y, w, h, img.format); + } else if (img.isLoaded()) { + tex.set(img.pixels, 0, 0, img.width, img.height, img.format); + } + } + img.setModified(false); + img.setLoaded(false); + } + + + protected void deleteSurfaceTextures() { + if (texture != null) { + texture.dispose(); + } + + if (ptexture != null) { + ptexture.dispose(); + } + + if (filterTexture != null) { + filterTexture.dispose(); + } + } + + + protected boolean checkGLThread() { + if (pgl.threadIsCurrent()) { + return true; + } else { + PGraphics.showWarning(OPENGL_THREAD_ERROR); + return false; + } + } + + + ////////////////////////////////////////////////////////////// + + // RESIZE + + + @Override + public void resize(int wide, int high) { + PGraphics.showMethodWarning("resize"); + } + + + ////////////////////////////////////////////////////////////// + + // INITIALIZATION ROUTINES + + + protected void initPrimary() { +// pgl.initSurface(quality); + if (texture != null) { + removeCache(this); + texture = ptexture = null; + } + initialized = true; + } + + + protected void beginOnscreenDraw() { + pgl.beginDraw(clearColorBuffer); + + if (drawFramebuffer == null) { + drawFramebuffer = new FrameBuffer(this, width, height, true); + } + drawFramebuffer.setFBO(pgl.getDrawFramebuffer()); + if (readFramebuffer == null) { + readFramebuffer = new FrameBuffer(this, width, height, true); + } + readFramebuffer.setFBO(pgl.getReadFramebuffer()); + if (currentFramebuffer == null) { + setFramebuffer(drawFramebuffer); + } + + if (pgl.isFBOBacked()) { + texture = pgl.wrapBackTexture(texture); + ptexture = pgl.wrapFrontTexture(ptexture); + } + } + + + protected void endOnscreenDraw() { + pgl.endDraw(clearColorBuffer0); + } + + + protected void initOffscreen() { + // Getting the context and capabilities from the main renderer. + loadTextureImpl(textureSampling, false); + + // In case of reinitialization (for example, when the smooth level + // is changed), we make sure that all the OpenGL resources associated + // to the surface are released by calling delete(). + if (offscreenFramebuffer != null) { + offscreenFramebuffer.dispose(); + } + if (multisampleFramebuffer != null) { + multisampleFramebuffer.dispose(); + } + + boolean packed = depthBits == 24 && stencilBits == 8 && + packedDepthStencilSupported; + if (PGraphicsOpenGL.fboMultisampleSupported && 1 < quality) { + multisampleFramebuffer = + new FrameBuffer(this, texture.glWidth, texture.glHeight, quality, 0, + depthBits, stencilBits, packed, false); + + multisampleFramebuffer.clear(); + offscreenMultisample = true; + + // The offscreen framebuffer where the multisampled image is finally drawn + // to doesn't need depth and stencil buffers since they are part of the + // multisampled framebuffer. + offscreenFramebuffer = + new FrameBuffer(this, texture.glWidth, texture.glHeight, 1, 1, 0, 0, + false, false); + + } else { + quality = 0; + offscreenFramebuffer = + new FrameBuffer(this, texture.glWidth, texture.glHeight, 1, 1, + depthBits, stencilBits, packed, false); + offscreenMultisample = false; + } + + offscreenFramebuffer.setColorBuffer(texture); + offscreenFramebuffer.clear(); + + initialized = true; + } + + + protected void beginOffscreenDraw() { + if (!initialized) { + initOffscreen(); + } else { + boolean outdated = offscreenFramebuffer != null && + offscreenFramebuffer.contextIsOutdated(); + boolean outdatedMulti = multisampleFramebuffer != null && + multisampleFramebuffer.contextIsOutdated(); + if (outdated || outdatedMulti) { + restartPGL(); + initOffscreen(); + } else { + // The back texture of the past frame becomes the front, + // and the front texture becomes the new back texture where the + // new frame is drawn to. + swapOffscreenTextures(); + } + } + + pushFramebuffer(); + if (offscreenMultisample) { + setFramebuffer(multisampleFramebuffer); + } else { + setFramebuffer(offscreenFramebuffer); + } + + // Render previous back texture (now is the front) as background + drawPTexture(); + + // Restoring the clipping configuration of the offscreen surface. + if (clip) { + pgl.enable(PGL.SCISSOR_TEST); + pgl.scissor(clipRect[0], clipRect[1], clipRect[2], clipRect[3]); + } else { + pgl.disable(PGL.SCISSOR_TEST); + } + } + + + protected void endOffscreenDraw() { + if (offscreenMultisample) { + multisampleFramebuffer.copyColor(offscreenFramebuffer); + } + + popFramebuffer(); + + if (backgroundA == 1) { + // Set alpha channel to opaque in order to match behavior of JAVA2D, not + // on the multisampled FBO because it leads to wrong background color + // on some Macbooks with AMD graphics. + pgl.colorMask(false, false, false, true); + pgl.clearColor(0, 0, 0, backgroundA); + pgl.clear(PGL.COLOR_BUFFER_BIT); + pgl.colorMask(true, true, true, true); + } + + texture.updateTexels(); // Mark all texels in screen texture as modified. + + getPrimaryPG().restoreGL(); + } + + + protected void setViewport() { + viewport.put(0, 0); viewport.put(1, 0); + viewport.put(2, width); viewport.put(3, height); + pgl.viewport(viewport.get(0), viewport.get(1), + viewport.get(2), viewport.get(3)); + } + + + protected void setDrawDefaults() { + inGeo.clear(); + tessGeo.clear(); + texCache.clear(); + + // Each frame starts with textures disabled. + super.noTexture(); + + // Making sure that OpenGL is using the last blend mode set by the user. + blendModeImpl(); + + // this is necessary for 3D drawing + if (hints[DISABLE_DEPTH_TEST]) { + pgl.disable(PGL.DEPTH_TEST); + } else { + pgl.enable(PGL.DEPTH_TEST); + } + // use <= since that's what processing.core does + pgl.depthFunc(PGL.LEQUAL); + + if (hints[DISABLE_OPTIMIZED_STROKE]) { + flushMode = FLUSH_CONTINUOUSLY; + } else { + flushMode = FLUSH_WHEN_FULL; + } + + if (primarySurface) { + pgl.getIntegerv(PGL.SAMPLES, intBuffer); + int temp = intBuffer.get(0); + if (quality != temp && 1 < temp && 1 < quality) { + // TODO check why the samples is higher that initialized smooth level. +// quality = temp; + } + } + if (quality < 2) { + pgl.disable(PGL.MULTISAMPLE); + } else { + pgl.enable(PGL.MULTISAMPLE); + } + pgl.disable(PGL.POLYGON_SMOOTH); + + if (sized) { +// reapplySettings(); + + // To avoid having garbage in the screen after a resize, + // in the case background is not called in draw(). + if (primarySurface) { + background(backgroundColor); + } else { + // offscreen surfaces are transparent by default. + background(0x00 << 24 | (backgroundColor & 0xFFFFFF)); + } + + // Sets the default projection and camera (initializes modelview). + // If the user has setup up their own projection, they'll need + // to fix it after resize anyway. This helps the people who haven't + // set up their own projection. + defaultPerspective(); + defaultCamera(); + + // clear the flag + sized = false; + } else { + // Eliminating any user's transformations by going back to the + // original camera setup. + modelview.set(camera); + modelviewInv.set(cameraInv); + updateProjmodelview(); + } + + if (is3D()) { + noLights(); + lightFalloff(1, 0, 0); + lightSpecular(0, 0, 0); + } + + // Vertices should be specified by user in CW order (left-handed) + // That is CCW order (right-handed). Vertex shader inverts + // Y-axis and outputs vertices in CW order (right-handed). + // Culling occurs after the vertex shader, so FRONT FACE + // has to be set to CW (right-handed) for OpenGL to correctly + // recognize FRONT and BACK faces. + pgl.frontFace(PGL.CW); + pgl.disable(PGL.CULL_FACE); + + // Processing uses only one texture unit. + pgl.activeTexture(PGL.TEXTURE0); + + // The current normal vector is set to be parallel to the Z axis. + normalX = normalY = 0; + normalZ = 1; + + // Clear depth and stencil buffers. + pgl.depthMask(true); + pgl.clearDepth(1); + pgl.clearStencil(0); + pgl.clear(PGL.DEPTH_BUFFER_BIT | PGL.STENCIL_BUFFER_BIT); + + if (!settingsInited) { + defaultSettings(); + } + + if (restoreSurface) { + restoreSurfaceFromPixels(); + restoreSurface = false; + } + + if (hints[DISABLE_DEPTH_MASK]) { + pgl.depthMask(false); + } else { + pgl.depthMask(true); + } + + pixelsOp = OP_NONE; + + clearColorBuffer0 = clearColorBuffer; + clearColorBuffer = false; + + modified = false; + arePixelsUpToDate = false; + } + + + protected void getGLParameters() { + OPENGL_VENDOR = pgl.getString(PGL.VENDOR); + OPENGL_RENDERER = pgl.getString(PGL.RENDERER); + OPENGL_VERSION = pgl.getString(PGL.VERSION); + OPENGL_EXTENSIONS = pgl.getString(PGL.EXTENSIONS); + GLSL_VERSION = pgl.getString(PGL.SHADING_LANGUAGE_VERSION); + + npotTexSupported = pgl.hasNpotTexSupport(); + autoMipmapGenSupported = pgl.hasAutoMipmapGenSupport(); + fboMultisampleSupported = pgl.hasFboMultisampleSupport(); + packedDepthStencilSupported = pgl.hasPackedDepthStencilSupport(); + anisoSamplingSupported = pgl.hasAnisoSamplingSupport(); + + try { + pgl.blendEquation(PGL.FUNC_ADD); + blendEqSupported = true; + } catch (Exception e) { + blendEqSupported = false; + } + + depthBits = pgl.getDepthBits(); + stencilBits = pgl.getStencilBits(); + + pgl.getIntegerv(PGL.MAX_TEXTURE_SIZE, intBuffer); + maxTextureSize = intBuffer.get(0); + + pgl.getIntegerv(PGL.MAX_SAMPLES, intBuffer); + maxSamples = intBuffer.get(0); + + if (anisoSamplingSupported) { + pgl.getFloatv(PGL.MAX_TEXTURE_MAX_ANISOTROPY, floatBuffer); + maxAnisoAmount = floatBuffer.get(0); + } + + glParamsRead = true; + } + + + ////////////////////////////////////////////////////////////// + + // SHADER HANDLING + + + @Override + public PShader loadShader(String fragFilename) { + if (fragFilename == null || fragFilename.equals("")) { + PGraphics.showWarning(MISSING_FRAGMENT_SHADER); + return null; + } + + int type = PShader.getShaderType(parent.loadStrings(fragFilename), + PShader.POLY); + PShader shader = new PShader(parent); + shader.setType(type); + shader.setFragmentShader(fragFilename); + if (type == PShader.POINT) { + String[] vertSource = pgl.loadVertexShader(defPointShaderVertURL, 120); + shader.setVertexShader(vertSource); + } else if (type == PShader.LINE) { + String[] vertSource = pgl.loadVertexShader(defLineShaderVertURL, 120); + shader.setVertexShader(vertSource); + } else if (type == PShader.TEXLIGHT) { + String[] vertSource = pgl.loadVertexShader(defTexlightShaderVertURL, 120); + shader.setVertexShader(vertSource); + } else if (type == PShader.LIGHT) { + String[] vertSource = pgl.loadVertexShader(defLightShaderVertURL, 120); + shader.setVertexShader(vertSource); + } else if (type == PShader.TEXTURE) { + String[] vertSource = pgl.loadVertexShader(defTextureShaderVertURL, 120); + shader.setVertexShader(vertSource); + } else if (type == PShader.COLOR) { + String[] vertSource = pgl.loadVertexShader(defColorShaderVertURL, 120); + shader.setVertexShader(vertSource); + } else { + String[] vertSource = pgl.loadVertexShader(defTextureShaderVertURL, 120); + shader.setVertexShader(vertSource); + } + return shader; + } + + + @Override + public PShader loadShader(String fragFilename, String vertFilename) { + if (fragFilename == null || fragFilename.equals("")) { + PGraphics.showWarning(MISSING_FRAGMENT_SHADER); + return null; + } else if (vertFilename == null || vertFilename.equals("")) { + PGraphics.showWarning(MISSING_VERTEX_SHADER); + return null; + } else { + return new PShader(parent, vertFilename, fragFilename); + } + } + + + @Override + public void shader(PShader shader) { + flush(); // Flushing geometry drawn with a different shader. + + if (shader.isPolyShader()) polyShader = shader; + else if (shader.isLineShader()) lineShader = shader; + else if (shader.isPointShader()) pointShader = shader; + else PGraphics.showWarning(UNKNOWN_SHADER_KIND_ERROR); + } + + + @Override + public void shader(PShader shader, int kind) { + flush(); // Flushing geometry drawn with a different shader. + + if (kind == TRIANGLES) polyShader = shader; + else if (kind == LINES) lineShader = shader; + else if (kind == POINTS) pointShader = shader; + else PGraphics.showWarning(UNKNOWN_SHADER_KIND_ERROR); + } + + + @Override + public void resetShader() { + resetShader(TRIANGLES); + } + + + @Override + public void resetShader(int kind) { + flush(); // Flushing geometry drawn with a different shader. + + if (kind == TRIANGLES || kind == QUADS || kind == POLYGON) { + polyShader = null; + } else if (kind == LINES) { + lineShader = null; + } else if (kind == POINTS) { + pointShader = null; + } else { + PGraphics.showWarning(UNKNOWN_SHADER_KIND_ERROR); + } + } + + + protected void deleteDefaultShaders() { + // The default shaders contains references to the PGraphics object that + // creates them, so when restarting the renderer, those references should + // dissapear. + defColorShader = null; + defTextureShader = null; + defLightShader = null; + defTexlightShader = null; + defLineShader = null; + defPointShader = null; + maskShader = null; + } + + + protected PShader getPolyShader(boolean lit, boolean tex) { + PShader shader; + PGraphicsOpenGL ppg = getPrimaryPG(); + boolean useDefault = polyShader == null; + if (polyShader != null) { + polyShader.setRenderer(this); + polyShader.loadAttributes(); + polyShader.loadUniforms(); + } + if (lit) { + if (tex) { + if (useDefault || !polyShader.checkPolyType(PShader.TEXLIGHT)) { + if (ppg.defTexlightShader == null) { + String[] vertSource = pgl.loadVertexShader(defTexlightShaderVertURL, 120); + String[] fragSource = pgl.loadFragmentShader(defTexlightShaderFragURL, 120); + ppg.defTexlightShader = new PShader(parent, vertSource, fragSource); + } + shader = ppg.defTexlightShader; + } else { + shader = polyShader; + } + } else { + if (useDefault || !polyShader.checkPolyType(PShader.LIGHT)) { + if (ppg.defLightShader == null) { + String[] vertSource = pgl.loadVertexShader(defLightShaderVertURL, 120); + String[] fragSource = pgl.loadFragmentShader(defLightShaderFragURL, 120); + ppg.defLightShader = new PShader(parent, vertSource, fragSource); + } + shader = ppg.defLightShader; + } else { + shader = polyShader; + } + } + } else { + if (polyShader != null && polyShader.accessLightAttribs()) { + PGraphics.showWarning(SHADER_NEED_LIGHT_ATTRIBS); + useDefault = true; + } + + if (tex) { + if (useDefault || !polyShader.checkPolyType(PShader.TEXTURE)) { + if (ppg.defTextureShader == null) { + String[] vertSource = pgl.loadVertexShader(defTextureShaderVertURL, 120); + String[] fragSource = pgl.loadFragmentShader(defTextureShaderFragURL, 120); + ppg.defTextureShader = new PShader(parent, vertSource, fragSource); + } + shader = ppg.defTextureShader; + } else { + shader = polyShader; + } + } else { + if (useDefault || !polyShader.checkPolyType(PShader.COLOR)) { + if (ppg.defColorShader == null) { + String[] vertSource = pgl.loadVertexShader(defColorShaderVertURL, 120); + String[] fragSource = pgl.loadFragmentShader(defColorShaderFragURL, 120); + ppg.defColorShader = new PShader(parent, vertSource, fragSource); + } + shader = ppg.defColorShader; + } else { + shader = polyShader; + } + } + } + if (shader != polyShader) { + shader.setRenderer(this); + shader.loadAttributes(); + shader.loadUniforms(); + } + return shader; + } + + + protected PShader getLineShader() { + PShader shader; + PGraphicsOpenGL ppg = getPrimaryPG(); + if (lineShader == null) { + if (ppg.defLineShader == null) { + String[] vertSource = pgl.loadVertexShader(defLineShaderVertURL, 120); + String[] fragSource = pgl.loadFragmentShader(defLineShaderFragURL, 120); + ppg.defLineShader = new PShader(parent, vertSource, fragSource); + } + shader = ppg.defLineShader; + } else { + shader = lineShader; + } + shader.setRenderer(this); + shader.loadAttributes(); + shader.loadUniforms(); + return shader; + } + + + protected PShader getPointShader() { + PShader shader; + PGraphicsOpenGL ppg = getPrimaryPG(); + if (pointShader == null) { + if (ppg.defPointShader == null) { + String[] vertSource = pgl.loadVertexShader(defPointShaderVertURL, 120); + String[] fragSource = pgl.loadFragmentShader(defPointShaderFragURL, 120); + ppg.defPointShader = new PShader(parent, vertSource, fragSource); + } + shader = ppg.defPointShader; + } else { + shader = pointShader; + } + shader.setRenderer(this); + shader.loadAttributes(); + shader.loadUniforms(); + return shader; + } + + + ////////////////////////////////////////////////////////////// + + // Utils + + static protected int expandArraySize(int currSize, int newMinSize) { + int newSize = currSize; + while (newSize < newMinSize) { + newSize <<= 1; + } + return newSize; + } + + ////////////////////////////////////////////////////////////// + + // Input (raw) and Tessellated geometry, tessellator. + + + static protected InGeometry newInGeometry(PGraphicsOpenGL pg, int mode) { + return new InGeometry(pg, mode); + } + + + static protected TessGeometry newTessGeometry(PGraphicsOpenGL pg, int mode) { + return new TessGeometry(pg, mode); + } + + + static protected TexCache newTexCache(PGraphicsOpenGL pg) { + return new TexCache(pg); + } + + + // Holds an array of textures and the range of vertex + // indices each texture applies to. + static protected class TexCache { + PGraphicsOpenGL pg; + int size; + PImage[] textures; + int[] firstIndex; + int[] lastIndex; + int[] firstCache; + int[] lastCache; + boolean hasTextures; + + TexCache(PGraphicsOpenGL pg) { + this.pg = pg; + allocate(); + } + + void allocate() { + textures = new PImage[PGL.DEFAULT_IN_TEXTURES]; + firstIndex = new int[PGL.DEFAULT_IN_TEXTURES]; + lastIndex = new int[PGL.DEFAULT_IN_TEXTURES]; + firstCache = new int[PGL.DEFAULT_IN_TEXTURES]; + lastCache = new int[PGL.DEFAULT_IN_TEXTURES]; + size = 0; + hasTextures = false; + } + + void clear() { + java.util.Arrays.fill(textures, 0, size, null); + size = 0; + hasTextures = false; + } + + boolean containsTexture(PImage img) { + for (int i = 0; i < size; i++) { + if (textures[i] == img) return true; + } + return false; + } + + PImage getTextureImage(int i) { + return textures[i]; + } + + Texture getTexture(int i) { + PImage img = textures[i]; + Texture tex = null; + + if (img != null) { + tex = pg.getTexture(img); + } + + return tex; + } + + void addTexture(PImage img, int firsti, int firstb, int lasti, int lastb) { + arrayCheck(); + + textures[size] = img; + firstIndex[size] = firsti; + lastIndex[size] = lasti; + firstCache[size] = firstb; + lastCache[size] = lastb; + + // At least one non-null texture since last reset. + hasTextures |= img != null; + + size++; + } + + void setLastIndex(int lasti, int lastb) { + lastIndex[size - 1] = lasti; + lastCache[size - 1] = lastb; + } + + void arrayCheck() { + if (size == textures.length) { + int newSize = size << 1; + + expandTextures(newSize); + expandFirstIndex(newSize); + expandLastIndex(newSize); + expandFirstCache(newSize); + expandLastCache(newSize); + } + } + + void expandTextures(int n) { + PImage[] temp = new PImage[n]; + PApplet.arrayCopy(textures, 0, temp, 0, size); + textures = temp; + } + + void expandFirstIndex(int n) { + int[] temp = new int[n]; + PApplet.arrayCopy(firstIndex, 0, temp, 0, size); + firstIndex = temp; + } + + void expandLastIndex(int n) { + int[] temp = new int[n]; + PApplet.arrayCopy(lastIndex, 0, temp, 0, size); + lastIndex = temp; + } + + void expandFirstCache(int n) { + int[] temp = new int[n]; + PApplet.arrayCopy(firstCache, 0, temp, 0, size); + firstCache = temp; + } + + void expandLastCache(int n) { + int[] temp = new int[n]; + PApplet.arrayCopy(lastCache, 0, temp, 0, size); + lastCache = temp; + } + } + + + // Stores the offsets and counts of indices and vertices + // to render a piece of geometry that doesn't fit in a single + // glDrawElements() call. + static protected class IndexCache { + int size; + int[] indexCount; + int[] indexOffset; + int[] vertexCount; + int[] vertexOffset; + + IndexCache() { + allocate(); + } + + void allocate() { + indexCount = new int[2]; + indexOffset = new int[2]; + vertexCount = new int[2]; + vertexOffset = new int[2]; + size = 0; + } + + void clear() { + size = 0; + } + + int addNew() { + arrayCheck(); + init(size); + size++; + return size - 1; + } + + int addNew(int index) { + arrayCheck(); + indexCount[size] = indexCount[index]; + indexOffset[size] = indexOffset[index]; + vertexCount[size] = vertexCount[index]; + vertexOffset[size] = vertexOffset[index]; + size++; + return size - 1; + } + + int getLast() { + if (size == 0) { + arrayCheck(); + init(0); + size = 1; + } + return size - 1; + } + + void incCounts(int index, int icount, int vcount) { + indexCount[index] += icount; + vertexCount[index] += vcount; + } + + void init(int n) { + if (0 < n) { + indexOffset[n] = indexOffset[n - 1] + indexCount[n - 1]; + vertexOffset[n] = vertexOffset[n - 1] + vertexCount[n - 1]; + } else { + indexOffset[n] = 0; + vertexOffset[n] = 0; + } + indexCount[n] = 0; + vertexCount[n] = 0; + } + + void arrayCheck() { + if (size == indexCount.length) { + int newSize = size << 1; + + expandIndexCount(newSize); + expandIndexOffset(newSize); + expandVertexCount(newSize); + expandVertexOffset(newSize); + } + } + + void expandIndexCount(int n) { + int[] temp = new int[n]; + PApplet.arrayCopy(indexCount, 0, temp, 0, size); + indexCount = temp; + } + + void expandIndexOffset(int n) { + int[] temp = new int[n]; + PApplet.arrayCopy(indexOffset, 0, temp, 0, size); + indexOffset = temp; + } + + void expandVertexCount(int n) { + int[] temp = new int[n]; + PApplet.arrayCopy(vertexCount, 0, temp, 0, size); + vertexCount = temp; + } + + void expandVertexOffset(int n) { + int[] temp = new int[n]; + PApplet.arrayCopy(vertexOffset, 0, temp, 0, size); + vertexOffset = temp; + } + } + + + // Holds the input vertices: xyz coordinates, fill/tint color, + // normal, texture coordinates and stroke color and weight. + static protected class InGeometry { + PGraphicsOpenGL pg; + int renderMode; + + int vertexCount; + int codeCount; + int edgeCount; + + float[] vertices; + int[] colors; + float[] normals; + float[] texcoords; + int[] strokeColors; + float[] strokeWeights; + + // vertex codes + int[] codes; + + // Stroke edges + int[][] edges; + + // Material properties + int[] ambient; + int[] specular; + int[] emissive; + float[] shininess; + + // Internally used by the addVertex() methods. + int fillColor; + int strokeColor; + float strokeWeight; + int ambientColor; + int specularColor; + int emissiveColor; + float shininessFactor; + float normalX, normalY, normalZ; + + InGeometry(PGraphicsOpenGL pg, int mode) { + this.pg = pg; + renderMode = mode; + allocate(); + } + + // ----------------------------------------------------------------- + // + // Allocate/dispose + + void clear() { + vertexCount = 0; + codeCount = 0; + edgeCount = 0; + } + + void clearEdges() { + edgeCount = 0; + } + + void allocate() { + vertices = new float[3 * PGL.DEFAULT_IN_VERTICES]; + colors = new int[PGL.DEFAULT_IN_VERTICES]; + normals = new float[3 * PGL.DEFAULT_IN_VERTICES]; + texcoords = new float[2 * PGL.DEFAULT_IN_VERTICES]; + strokeColors = new int[PGL.DEFAULT_IN_VERTICES]; + strokeWeights = new float[PGL.DEFAULT_IN_VERTICES]; + ambient = new int[PGL.DEFAULT_IN_VERTICES]; + specular = new int[PGL.DEFAULT_IN_VERTICES]; + emissive = new int[PGL.DEFAULT_IN_VERTICES]; + shininess = new float[PGL.DEFAULT_IN_VERTICES]; + edges = new int[PGL.DEFAULT_IN_EDGES][3]; + + clear(); + } + + void vertexCheck() { + if (vertexCount == vertices.length / 3) { + int newSize = vertexCount << 1; + + expandVertices(newSize); + expandColors(newSize); + expandNormals(newSize); + expandTexCoords(newSize); + expandStrokeColors(newSize); + expandStrokeWeights(newSize); + expandAmbient(newSize); + expandSpecular(newSize); + expandEmissive(newSize); + expandShininess(newSize); + } + } + + void codeCheck() { + if (codeCount == codes.length) { + int newLen = codeCount << 1; + + expandCodes(newLen); + } + } + + void edgeCheck() { + if (edgeCount == edges.length) { + int newLen = edgeCount << 1; + + expandEdges(newLen); + } + } + + // ----------------------------------------------------------------- + // + // Query + + float getVertexX(int idx) { + return vertices[3 * idx + 0]; + } + + float getVertexY(int idx) { + return vertices[3 * idx + 1]; + } + + float getVertexZ(int idx) { + return vertices[3 * idx + 2]; + } + + float getLastVertexX() { + return vertices[3 * (vertexCount - 1) + 0]; + } + + float getLastVertexY() { + return vertices[3 * (vertexCount - 1) + 1]; + } + + float getLastVertexZ() { + return vertices[3 * (vertexCount - 1) + 2]; + } + + int getNumEdgeClosures() { + int count = 0; + for (int i = 0; i < edgeCount; i++) { + if (edges[i][2] == EDGE_CLOSE) count++; + } + return count; + } + + int getNumEdgeVertices(boolean bevel) { + int segVert = edgeCount; + int bevVert = 0; + if (bevel) { + for (int i = 0; i < edgeCount; i++) { + int[] edge = edges[i]; + if (edge[2] == EDGE_MIDDLE || edge[2] == EDGE_START) bevVert++; + if (edge[2] == EDGE_CLOSE) segVert--; + } + } else { + segVert -= getNumEdgeClosures(); + } + return 4 * segVert + bevVert; + } + + int getNumEdgeIndices(boolean bevel) { + int segInd = edgeCount; + int bevInd = 0; + if (bevel) { + for (int i = 0; i < edgeCount; i++) { + int[] edge = edges[i]; + if (edge[2] == EDGE_MIDDLE || edge[2] == EDGE_START) bevInd++; + if (edge[2] == EDGE_CLOSE) segInd--; + } + } else { + segInd -= getNumEdgeClosures(); + } + return 6 * (segInd + bevInd); + } + + void getVertexMin(PVector v) { + int index; + for (int i = 0; i < vertexCount; i++) { + index = 4 * i; + v.x = PApplet.min(v.x, vertices[index++]); + v.y = PApplet.min(v.y, vertices[index++]); + v.z = PApplet.min(v.z, vertices[index ]); + } + } + + void getVertexMax(PVector v) { + int index; + for (int i = 0; i < vertexCount; i++) { + index = 4 * i; + v.x = PApplet.max(v.x, vertices[index++]); + v.y = PApplet.max(v.y, vertices[index++]); + v.z = PApplet.max(v.z, vertices[index ]); + } + } + + int getVertexSum(PVector v) { + int index; + for (int i = 0; i < vertexCount; i++) { + index = 4 * i; + v.x += vertices[index++]; + v.y += vertices[index++]; + v.z += vertices[index ]; + } + return vertexCount; + } + + // ----------------------------------------------------------------- + // + // Expand arrays + + void expandVertices(int n) { + float temp[] = new float[3 * n]; + PApplet.arrayCopy(vertices, 0, temp, 0, 3 * vertexCount); + vertices = temp; + } + + void expandColors(int n) { + int temp[] = new int[n]; + PApplet.arrayCopy(colors, 0, temp, 0, vertexCount); + colors = temp; + } + + void expandNormals(int n) { + float temp[] = new float[3 * n]; + PApplet.arrayCopy(normals, 0, temp, 0, 3 * vertexCount); + normals = temp; + } + + void expandTexCoords(int n) { + float temp[] = new float[2 * n]; + PApplet.arrayCopy(texcoords, 0, temp, 0, 2 * vertexCount); + texcoords = temp; + } + + void expandStrokeColors(int n) { + int temp[] = new int[n]; + PApplet.arrayCopy(strokeColors, 0, temp, 0, vertexCount); + strokeColors = temp; + } + + void expandStrokeWeights(int n) { + float temp[] = new float[n]; + PApplet.arrayCopy(strokeWeights, 0, temp, 0, vertexCount); + strokeWeights = temp; + } + + void expandAmbient(int n) { + int temp[] = new int[n]; + PApplet.arrayCopy(ambient, 0, temp, 0, vertexCount); + ambient = temp; + } + + void expandSpecular(int n) { + int temp[] = new int[n]; + PApplet.arrayCopy(specular, 0, temp, 0, vertexCount); + specular = temp; + } + + void expandEmissive(int n) { + int temp[] = new int[n]; + PApplet.arrayCopy(emissive, 0, temp, 0, vertexCount); + emissive = temp; + } + + void expandShininess(int n) { + float temp[] = new float[n]; + PApplet.arrayCopy(shininess, 0, temp, 0, vertexCount); + shininess = temp; + } + + void expandCodes(int n) { + int temp[] = new int[n]; + PApplet.arrayCopy(codes, 0, temp, 0, codeCount); + codes = temp; + } + + void expandEdges(int n) { + int temp[][] = new int[n][3]; + PApplet.arrayCopy(edges, 0, temp, 0, edgeCount); + edges = temp; + } + + // ----------------------------------------------------------------- + // + // Trim arrays + + void trim() { + if (0 < vertexCount && vertexCount < vertices.length / 3) { + trimVertices(); + trimColors(); + trimNormals(); + trimTexCoords(); + trimStrokeColors(); + trimStrokeWeights(); + trimAmbient(); + trimSpecular(); + trimEmissive(); + trimShininess(); + } + + if (0 < codeCount && codeCount < codes.length) { + trimCodes(); + } + + if (0 < edgeCount && edgeCount < edges.length) { + trimEdges(); + } + } + + void trimVertices() { + float temp[] = new float[3 * vertexCount]; + PApplet.arrayCopy(vertices, 0, temp, 0, 3 * vertexCount); + vertices = temp; + } + + void trimColors() { + int temp[] = new int[vertexCount]; + PApplet.arrayCopy(colors, 0, temp, 0, vertexCount); + colors = temp; + } + + void trimNormals() { + float temp[] = new float[3 * vertexCount]; + PApplet.arrayCopy(normals, 0, temp, 0, 3 * vertexCount); + normals = temp; + } + + void trimTexCoords() { + float temp[] = new float[2 * vertexCount]; + PApplet.arrayCopy(texcoords, 0, temp, 0, 2 * vertexCount); + texcoords = temp; + } + + void trimStrokeColors() { + int temp[] = new int[vertexCount]; + PApplet.arrayCopy(strokeColors, 0, temp, 0, vertexCount); + strokeColors = temp; + } + + void trimStrokeWeights() { + float temp[] = new float[vertexCount]; + PApplet.arrayCopy(strokeWeights, 0, temp, 0, vertexCount); + strokeWeights = temp; + } + + void trimAmbient() { + int temp[] = new int[vertexCount]; + PApplet.arrayCopy(ambient, 0, temp, 0, vertexCount); + ambient = temp; + } + + void trimSpecular() { + int temp[] = new int[vertexCount]; + PApplet.arrayCopy(specular, 0, temp, 0, vertexCount); + specular = temp; + } + + void trimEmissive() { + int temp[] = new int[vertexCount]; + PApplet.arrayCopy(emissive, 0, temp, 0, vertexCount); + emissive = temp; + } + + void trimShininess() { + float temp[] = new float[vertexCount]; + PApplet.arrayCopy(shininess, 0, temp, 0, vertexCount); + shininess = temp; + } + + void trimCodes() { + int temp[] = new int[codeCount]; + PApplet.arrayCopy(codes, 0, temp, 0, codeCount); + codes = temp; + } + + void trimEdges() { + int temp[][] = new int[edgeCount][3]; + PApplet.arrayCopy(edges, 0, temp, 0, edgeCount); + edges = temp; + } + + // ----------------------------------------------------------------- + // + // Vertices + + int addVertex(float x, float y, boolean brk) { + return addVertex(x, y, VERTEX, brk); + } + + int addVertex(float x, float y, + int code, boolean brk) { + return addVertex(x, y, 0, + fillColor, + normalX, normalY, normalZ, + 0, 0, + strokeColor, strokeWeight, + ambientColor, specularColor, emissiveColor, + shininessFactor, + code, brk); + } + + int addVertex(float x, float y, + float u, float v, + boolean brk) { + return addVertex(x, y, u, v, VERTEX, brk); + } + + int addVertex(float x, float y, + float u, float v, + int code, boolean brk) { + return addVertex(x, y, 0, + fillColor, + normalX, normalY, normalZ, + u, v, + strokeColor, strokeWeight, + ambientColor, specularColor, emissiveColor, + shininessFactor, + code, brk); + } + + int addVertex(float x, float y, float z, boolean brk) { + return addVertex(x, y, z, VERTEX, brk); + } + + int addVertex(float x, float y, float z, + int code, boolean brk) { + return addVertex(x, y, z, + fillColor, + normalX, normalY, normalZ, + 0, 0, + strokeColor, strokeWeight, + ambientColor, specularColor, emissiveColor, + shininessFactor, + code, brk); + } + + int addVertex(float x, float y, float z, + float u, float v, + boolean brk) { + return addVertex(x, y, z, u, v, VERTEX, brk); + } + + int addVertex(float x, float y, float z, + float u, float v, + int code, boolean brk) { + return addVertex(x, y, z, + fillColor, + normalX, normalY, normalZ, + u, v, + strokeColor, strokeWeight, + ambientColor, specularColor, emissiveColor, + shininessFactor, + code, brk); + } + + int addVertex(float x, float y, float z, + int fcolor, + float nx, float ny, float nz, + float u, float v, + int scolor, float sweight, + int am, int sp, int em, float shine, + int code, boolean brk) { + vertexCheck(); + int index; + + index = 3 * vertexCount; + vertices[index++] = x; + vertices[index++] = y; + vertices[index ] = z; + + colors[vertexCount] = PGL.javaToNativeARGB(fcolor); + + index = 3 * vertexCount; + normals[index++] = nx; + normals[index++] = ny; + normals[index ] = nz; + + index = 2 * vertexCount; + texcoords[index++] = u; + texcoords[index ] = v; + + strokeColors[vertexCount] = PGL.javaToNativeARGB(scolor); + strokeWeights[vertexCount] = sweight; + + ambient[vertexCount] = PGL.javaToNativeARGB(am); + specular[vertexCount] = PGL.javaToNativeARGB(sp); + emissive[vertexCount] = PGL.javaToNativeARGB(em); + shininess[vertexCount] = shine; + + if (brk || (code == VERTEX && codes != null) || + code == BEZIER_VERTEX || + code == QUADRATIC_VERTEX || + code == CURVE_VERTEX) { + if (codes == null) { + codes = new int[PApplet.max(PGL.DEFAULT_IN_VERTICES, vertexCount)]; + Arrays.fill(codes, 0, vertexCount, VERTEX); + codeCount = vertexCount; + } + + if (brk) { + codeCheck(); + codes[codeCount] = BREAK; + codeCount++; + } + + if (code != -1) { + codeCheck(); + codes[codeCount] = code; + codeCount++; + } + } + + vertexCount++; + + return vertexCount - 1; + } + + public void addBezierVertex(float x2, float y2, float z2, + float x3, float y3, float z3, + float x4, float y4, float z4, boolean brk) { + addVertex(x2, y2, z2, BEZIER_VERTEX, brk); + addVertex(x3, y3, z3, -1, false); + addVertex(x4, y4, z4, -1, false); + } + + public void addQuadraticVertex(float cx, float cy, float cz, + float x3, float y3, float z3, boolean brk) { + addVertex(cx, cy, cz, QUADRATIC_VERTEX, brk); + addVertex(x3, y3, z3, -1, false); + } + + public void addCurveVertex(float x, float y, float z, boolean brk) { + addVertex(x, y, z, CURVE_VERTEX, brk); + } + + // Returns the vertex data in the PGraphics double array format. + float[][] getVertexData() { + float[][] data = new float[vertexCount][VERTEX_FIELD_COUNT]; + for (int i = 0; i < vertexCount; i++) { + float[] vert = data[i]; + + vert[X] = vertices[3 * i + 0]; + vert[Y] = vertices[3 * i + 1]; + vert[Z] = vertices[3 * i + 2]; + + vert[R] = ((colors[i] >> 16) & 0xFF) / 255.0f; + vert[G] = ((colors[i] >> 8) & 0xFF) / 255.0f; + vert[B] = ((colors[i] >> 0) & 0xFF) / 255.0f; + vert[A] = ((colors[i] >> 24) & 0xFF) / 255.0f; + + vert[U] = texcoords[2 * i + 0]; + vert[V] = texcoords[2 * i + 1]; + + vert[NX] = normals[3 * i + 0]; + vert[NY] = normals[3 * i + 1]; + vert[NZ] = normals[3 * i + 2]; + + vert[SR] = ((strokeColors[i] >> 16) & 0xFF) / 255.0f; + vert[SG] = ((strokeColors[i] >> 8) & 0xFF) / 255.0f; + vert[SB] = ((strokeColors[i] >> 0) & 0xFF) / 255.0f; + vert[SA] = ((strokeColors[i] >> 24) & 0xFF) / 255.0f; + + vert[SW] = strokeWeights[i]; + } + + return data; + } + + boolean hasBezierVertex() { + for (int i = 0; i < codeCount; i++) { + if (codes[i] == BEZIER_VERTEX) return true; + } + return false; + } + + boolean hasQuadraticVertex() { + for (int i = 0; i < codeCount; i++) { + if (codes[i] == QUADRATIC_VERTEX) return true; + } + return false; + } + + boolean hasCurveVertex() { + for (int i = 0; i < codeCount; i++) { + if (codes[i] == CURVE_VERTEX) return true; + } + return false; + } + + // ----------------------------------------------------------------- + // + // Edges + + int addEdge(int i, int j, boolean start, boolean end) { + edgeCheck(); + + int[] edge = edges[edgeCount]; + edge[0] = i; + edge[1] = j; + + // Possible values for state: + // 0 = middle edge (not start, not end) + // 1 = start edge (start, not end) + // 2 = end edge (not start, end) + // 3 = isolated edge (start, end) + edge[2] = (start ? 1 : 0) + 2 * (end ? 1 : 0); + + edgeCount++; + + return edgeCount - 1; + } + + int closeEdge(int i, int j) { + edgeCheck(); + + int[] edge = edges[edgeCount]; + edge[0] = i; + edge[1] = j; + edge[2] = EDGE_CLOSE; + + edgeCount++; + + return edgeCount - 1; + } + + void addTrianglesEdges() { + for (int i = 0; i < vertexCount / 3; i++) { + int i0 = 3 * i + 0; + int i1 = 3 * i + 1; + int i2 = 3 * i + 2; + + addEdge(i0, i1, true, false); + addEdge(i1, i2, false, false); + addEdge(i2, i0, false, false); + closeEdge(i2, i0); + } + } + + void addTriangleFanEdges() { + for (int i = 1; i < vertexCount - 1; i++) { + int i0 = 0; + int i1 = i; + int i2 = i + 1; + + addEdge(i0, i1, true, false); + addEdge(i1, i2, false, false); + addEdge(i2, i0, false, false); + closeEdge(i2, i0); + } + } + + void addTriangleStripEdges() { + for (int i = 1; i < vertexCount - 1; i++) { + int i0 = i; + int i1, i2; + if (i % 2 == 0) { + i1 = i - 1; + i2 = i + 1; + } else { + i1 = i + 1; + i2 = i - 1; + } + + addEdge(i0, i1, true, false); + addEdge(i1, i2, false, false); + addEdge(i2, i0, false, false); + closeEdge(i2, i0); + } + } + + void addQuadsEdges() { + for (int i = 0; i < vertexCount / 4; i++) { + int i0 = 4 * i + 0; + int i1 = 4 * i + 1; + int i2 = 4 * i + 2; + int i3 = 4 * i + 3; + + addEdge(i0, i1, true, false); + addEdge(i1, i2, false, false); + addEdge(i2, i3, false, false); + addEdge(i3, i0, false, false); + closeEdge(i3, i0); + } + } + + void addQuadStripEdges() { + for (int qd = 1; qd < vertexCount / 2; qd++) { + int i0 = 2 * (qd - 1); + int i1 = 2 * (qd - 1) + 1; + int i2 = 2 * qd + 1; + int i3 = 2 * qd; + + addEdge(i0, i1, true, false); + addEdge(i1, i2, false, false); + addEdge(i2, i3, false, false); + addEdge(i3, i0, false, true); + closeEdge(i3, i0); + } + } + + // ----------------------------------------------------------------- + // + // Normal calculation + + // Expects vertices in CW (left-handed) order. + void calcTriangleNormal(int i0, int i1, int i2) { + int index; + + index = 3 * i0; + float x0 = vertices[index++]; + float y0 = vertices[index++]; + float z0 = vertices[index ]; + + index = 3 * i1; + float x1 = vertices[index++]; + float y1 = vertices[index++]; + float z1 = vertices[index ]; + + index = 3 * i2; + float x2 = vertices[index++]; + float y2 = vertices[index++]; + float z2 = vertices[index ]; + + float v12x = x2 - x1; + float v12y = y2 - y1; + float v12z = z2 - z1; + + float v10x = x0 - x1; + float v10y = y0 - y1; + float v10z = z0 - z1; + + // The automatic normal calculation in Processing assumes + // that vertices as given in CCW order (right-handed) so: + // n = v12 x v10 + // so that the normal extends from the front face. + float nx = v12y * v10z - v10y * v12z; + float ny = v12z * v10x - v10z * v12x; + float nz = v12x * v10y - v10x * v12y; + float d = PApplet.sqrt(nx * nx + ny * ny + nz * nz); + nx /= d; + ny /= d; + nz /= d; + + index = 3 * i0; + normals[index++] = nx; + normals[index++] = ny; + normals[index ] = nz; + + index = 3 * i1; + normals[index++] = nx; + normals[index++] = ny; + normals[index ] = nz; + + index = 3 * i2; + normals[index++] = nx; + normals[index++] = ny; + normals[index ] = nz; + } + + void calcTrianglesNormals() { + for (int i = 0; i < vertexCount / 3; i++) { + int i0 = 3 * i + 0; + int i1 = 3 * i + 1; + int i2 = 3 * i + 2; + + calcTriangleNormal(i0, i1, i2); + } + } + + void calcTriangleFanNormals() { + for (int i = 1; i < vertexCount - 1; i++) { + int i0 = 0; + int i1 = i; + int i2 = i + 1; + + calcTriangleNormal(i0, i1, i2); + } + } + + void calcTriangleStripNormals() { + for (int i = 1; i < vertexCount - 1; i++) { + int i1 = i; + int i0, i2; + // Vertices are specified by user as: + // 1-3 ... + // |\|\ ... + // 0-2-4 ... + if (i % 2 == 1) { + // The odd triangles (1, 3, 5...) should be CW (left-handed) + i0 = i - 1; + i2 = i + 1; + } else { + // The even triangles (2, 4, 6...) should be CCW (left-handed) + i0 = i + 1; + i2 = i - 1; + } + calcTriangleNormal(i0, i1, i2); + } + } + + void calcQuadsNormals() { + for (int i = 0; i < vertexCount / 4; i++) { + int i0 = 4 * i + 0; + int i1 = 4 * i + 1; + int i2 = 4 * i + 2; + int i3 = 4 * i + 3; + + calcTriangleNormal(i0, i1, i2); + calcTriangleNormal(i2, i3, i0); + } + } + + void calcQuadStripNormals() { + for (int qd = 1; qd < vertexCount / 2; qd++) { + int i0 = 2 * (qd - 1); + int i1 = 2 * (qd - 1) + 1; + int i2 = 2 * qd; + int i3 = 2 * qd + 1; + + // Vertices are specified by user as: + // 1-3-5 ... + // |\|\| ... + // 0-2-4 ... + // thus (0, 1, 2) and (2, 1, 3) are triangles + // in CW order (left-handed). + calcTriangleNormal(i0, i1, i2); + calcTriangleNormal(i2, i1, i3); + } + } + + // ----------------------------------------------------------------- + // + // Primitives + + void setMaterial(int fillColor, int strokeColor, float strokeWeight, + int ambientColor, int specularColor, int emissiveColor, + float shininessFactor) { + this.fillColor = fillColor; + this.strokeColor = strokeColor; + this.strokeWeight = strokeWeight; + this.ambientColor = ambientColor; + this.specularColor = specularColor; + this.emissiveColor = emissiveColor; + this.shininessFactor = shininessFactor; + } + + void setNormal(float normalX, float normalY, float normalZ) { + this.normalX = normalX; + this.normalY = normalY; + this.normalZ = normalZ; + } + + void addPoint(float x, float y, float z, boolean fill, boolean stroke) { + addVertex(x, y, z, VERTEX, true); + } + + void addLine(float x1, float y1, float z1, + float x2, float y2, float z2, + boolean fill, boolean stroke) { + int idx1 = addVertex(x1, y1, z1, VERTEX, true); + int idx2 = addVertex(x2, y2, z2, VERTEX, false); + if (stroke) addEdge(idx1, idx2, true, true); + } + + void addTriangle(float x1, float y1, float z1, + float x2, float y2, float z2, + float x3, float y3, float z3, + boolean fill, boolean stroke) { + int idx1 = addVertex(x1, y1, z1, VERTEX, true); + int idx2 = addVertex(x2, y2, z2, VERTEX, false); + int idx3 = addVertex(x3, y3, z3, VERTEX, false); + if (stroke) { + addEdge(idx1, idx2, true, false); + addEdge(idx2, idx3, false, false); + addEdge(idx3, idx1, false, false); + closeEdge(idx3, idx1); + } + } + + void addQuad(float x1, float y1, float z1, + float x2, float y2, float z2, + float x3, float y3, float z3, + float x4, float y4, float z4, + boolean stroke) { + int idx1 = addVertex(x1, y1, z1, 0, 0, VERTEX, true); + int idx2 = addVertex(x2, y2, z2, 1, 0, VERTEX, false); + int idx3 = addVertex(x3, y3, z3, 1, 1, VERTEX, false); + int idx4 = addVertex(x4, y4, z4, 0, 1, VERTEX, false); + if (stroke) { + addEdge(idx1, idx2, true, false); + addEdge(idx2, idx3, false, false); + addEdge(idx3, idx4, false, false); + addEdge(idx4, idx1, false, false); + closeEdge(idx4, idx1); + } + } + + void addRect(float a, float b, float c, float d, + boolean stroke) { + addQuad(a, b, 0, + c, b, 0, + c, d, 0, + a, d, 0, + stroke); + } + + void addRect(float a, float b, float c, float d, + float tl, float tr, float br, float bl, + boolean stroke) { + if (nonZero(tr)) { + addVertex(c-tr, b, VERTEX, true); + addQuadraticVertex(c, b, 0, c, b+tr, 0, false); + } else { + addVertex(c, b, VERTEX, true); + } + if (nonZero(br)) { + addVertex(c, d-br, VERTEX, false); + addQuadraticVertex(c, d, 0, c-br, d, 0, false); + } else { + addVertex(c, d, VERTEX, false); + } + if (nonZero(bl)) { + addVertex(a+bl, d, VERTEX, false); + addQuadraticVertex(a, d, 0, a, d-bl, 0, false); + } else { + addVertex(a, d, VERTEX, false); + } + if (nonZero(tl)) { + addVertex(a, b+tl, VERTEX, false); + addQuadraticVertex(a, b, 0, a+tl, b, 0, false); + } else { + addVertex(a, b, VERTEX, false); + } + } + + void addEllipse(float x, float y, float w, float h, + boolean fill, boolean stroke) { + float radiusH = w / 2; + float radiusV = h / 2; + + float centerX = x + radiusH; + float centerY = y + radiusV; + + // should call screenX/Y using current renderer. + float sx1 = pg.screenX(x, y); + float sy1 = pg.screenY(x, y); + float sx2 = pg.screenX(x + w, y + h); + float sy2 = pg.screenY(x + w, y + h); + + int accuracy = + PApplet.min(MAX_POINT_ACCURACY, PApplet.max(MIN_POINT_ACCURACY, + (int) (TWO_PI * PApplet.dist(sx1, sy1, sx2, sy2) / + POINT_ACCURACY_FACTOR))); + float inc = (float) SINCOS_LENGTH / accuracy; + + if (fill) { + addVertex(centerX, centerY, VERTEX, true); + } + int idx0, pidx, idx; + idx0 = pidx = idx = 0; + float val = 0; + for (int i = 0; i < accuracy; i++) { + idx = addVertex(centerX + cosLUT[(int) val] * radiusH, + centerY + sinLUT[(int) val] * radiusV, + VERTEX, i == 0 && !fill); + val = (val + inc) % SINCOS_LENGTH; + + if (0 < i) { + if (stroke) addEdge(pidx, idx, i == 1, false); + } else { + idx0 = idx; + } + + pidx = idx; + } + // Back to the beginning + addVertex(centerX + cosLUT[0] * radiusH, + centerY + sinLUT[0] * radiusV, + VERTEX, false); + if (stroke) { + addEdge(idx, idx0, false, false); + closeEdge(idx, idx0); + } + } + + // arcMode can be 0, OPEN, CHORD, or PIE + void addArc(float x, float y, float w, float h, + float start, float stop, + boolean fill, boolean stroke, int arcMode) { + float hr = w / 2f; + float vr = h / 2f; + + float centerX = x + hr; + float centerY = y + vr; + + int startLUT = (int) (0.5f + (start / TWO_PI) * SINCOS_LENGTH); + int stopLUT = (int) (0.5f + (stop / TWO_PI) * SINCOS_LENGTH); + + // get length before wrapping indexes so (startLUT <= stopLUT); + int length = PApplet.constrain(stopLUT - startLUT, 0, SINCOS_LENGTH); + + boolean fullCircle = length == SINCOS_LENGTH; + + if (fullCircle && arcMode == CHORD) { + // get rid of overlapping vertices, + // solves problem with closing edge in P3D + length -= 1; + stopLUT -= 1; + } + + { // wrap indexes so they are safe to use in LUT + startLUT %= SINCOS_LENGTH; + if (startLUT < 0) startLUT += SINCOS_LENGTH; + + stopLUT %= SINCOS_LENGTH; + if (stopLUT < 0) stopLUT += SINCOS_LENGTH; + } + + int idx0; + if (arcMode == CHORD || arcMode == OPEN) { + // move center to the middle of flat side + // to properly display arcs smaller than PI + float relX = (cosLUT[startLUT] + cosLUT[stopLUT]) * 0.5f * hr; + float relY = (sinLUT[startLUT] + sinLUT[stopLUT]) * 0.5f * vr; + idx0 = addVertex(centerX + relX, centerY + relY, VERTEX, true); + } else { + idx0 = addVertex(centerX, centerY, VERTEX, true); + } + + int inc; + { // initializes inc the same way ellipse does + float sx1 = pg.screenX(x, y); + float sy1 = pg.screenY(x, y); + float sx2 = pg.screenX(x + w, y + h); + float sy2 = pg.screenY(x + w, y + h); + + int accuracy = + PApplet.min(MAX_POINT_ACCURACY, PApplet.max(MIN_POINT_ACCURACY, + (int) (TWO_PI * PApplet.dist(sx1, sy1, sx2, sy2) / + POINT_ACCURACY_FACTOR))); + inc = PApplet.max(1, SINCOS_LENGTH / accuracy); + } + + int idx = idx0; + int pidx; + + int i = -inc; + int ii; + + // i: (0 -> length) inclusive + // ii: (startLUT -> stopLUT) inclusive, going CW (left-handed), + // wrapping around end of LUT + do { + i += inc; + i = PApplet.min(i, length); // clamp so last vertex won't go over + + ii = startLUT + i; // ii from 0 to (2 * SINCOS_LENGTH - 1) + if (ii >= SINCOS_LENGTH) ii -= SINCOS_LENGTH; + + pidx = idx; + idx = addVertex(centerX + cosLUT[ii] * hr, + centerY + sinLUT[ii] * vr, + VERTEX, i == 0 && !fill); + + if (stroke) { + if (arcMode == CHORD || arcMode == PIE) { + addEdge(pidx, idx, i == 0, false); + } else if (0 < i) { + // when drawing full circle, the edge is closed later + addEdge(pidx, idx, i == PApplet.min(inc, length), + i == length && !fullCircle); + } + } + } while (i < length); + + // keeping last vertex as idx and second last vertex as pidx + + if (stroke) { + if (arcMode == CHORD || arcMode == PIE) { + addEdge(idx, idx0, false, false); + closeEdge(idx, idx0); + } else if (fullCircle) { + closeEdge(pidx, idx); + } + } + } + + void addBox(float w, float h, float d, + boolean fill, boolean stroke) { + + // Correct normals if some dimensions are negative so they always + // extend from front face. We could just take absolute value + // of dimensions, but that would affect texturing. + boolean invertNormX = (h > 0) != (d > 0); + boolean invertNormY = (w > 0) != (d > 0); + boolean invertNormZ = (w > 0) != (h > 0); + + int normX = invertNormX ? -1 : 1; + int normY = invertNormY ? -1 : 1; + int normZ = invertNormZ ? -1 : 1; + + float x1 = -w/2f; float x2 = w/2f; + float y1 = -h/2f; float y2 = h/2f; + float z1 = -d/2f; float z2 = d/2f; + + int idx1 = 0, idx2 = 0, idx3 = 0, idx4 = 0; + if (fill || stroke) { + // back face + setNormal(0, 0, -normZ); + idx1 = addVertex(x1, y1, z1, 0, 0, VERTEX, true); + idx2 = addVertex(x1, y2, z1, 0, 1, VERTEX, false); + idx3 = addVertex(x2, y2, z1, 1, 1, VERTEX, false); + idx4 = addVertex(x2, y1, z1, 1, 0, VERTEX, false); + if (stroke) { + addEdge(idx1, idx2, true, false); + addEdge(idx2, idx3, false, false); + addEdge(idx3, idx4, false, false); + addEdge(idx4, idx1, false, false); + closeEdge(idx4, idx1); + } + + // front face + setNormal(0, 0, normZ); + idx1 = addVertex(x1, y2, z2, 1, 1, VERTEX, false); + idx2 = addVertex(x1, y1, z2, 1, 0, VERTEX, false); + idx3 = addVertex(x2, y1, z2, 0, 0, VERTEX, false); + idx4 = addVertex(x2, y2, z2, 0, 1, VERTEX, false); + if (stroke) { + addEdge(idx1, idx2, true, false); + addEdge(idx2, idx3, false, false); + addEdge(idx3, idx4, false, false); + addEdge(idx4, idx1, false, false); + closeEdge(idx4, idx1); + } + + // right face + setNormal(normX, 0, 0); + idx1 = addVertex(x2, y1, z1, 0, 0, VERTEX, false); + idx2 = addVertex(x2, y2, z1, 0, 1, VERTEX, false); + idx3 = addVertex(x2, y2, z2, 1, 1, VERTEX, false); + idx4 = addVertex(x2, y1, z2, 1, 0, VERTEX, false); + if (stroke) { + addEdge(idx1, idx2, true, false); + addEdge(idx2, idx3, false, false); + addEdge(idx3, idx4, false, false); + addEdge(idx4, idx1, false, false); + closeEdge(idx4, idx1); + } + + // left face + setNormal(-normX, 0, 0); + idx1 = addVertex(x1, y2, z1, 1, 1, VERTEX, false); + idx2 = addVertex(x1, y1, z1, 1, 0, VERTEX, false); + idx3 = addVertex(x1, y1, z2, 0, 0, VERTEX, false); + idx4 = addVertex(x1, y2, z2, 0, 1, VERTEX, false); + if (stroke) { + addEdge(idx1, idx2, true, false); + addEdge(idx2, idx3, false, false); + addEdge(idx3, idx4, false, false); + addEdge(idx4, idx1, false, false); + closeEdge(idx4, idx1); + } + + // top face + setNormal(0, -normY, 0); + idx1 = addVertex(x2, y1, z1, 1, 1, VERTEX, false); + idx2 = addVertex(x2, y1, z2, 1, 0, VERTEX, false); + idx3 = addVertex(x1, y1, z2, 0, 0, VERTEX, false); + idx4 = addVertex(x1, y1, z1, 0, 1, VERTEX, false); + if (stroke) { + addEdge(idx1, idx2, true, false); + addEdge(idx2, idx3, false, false); + addEdge(idx3, idx4, false, false); + addEdge(idx4, idx1, false, false); + closeEdge(idx4, idx1); + } + + // bottom face + setNormal(0, normY, 0); + idx1 = addVertex(x1, y2, z1, 0, 0, VERTEX, false); + idx2 = addVertex(x1, y2, z2, 0, 1, VERTEX, false); + idx3 = addVertex(x2, y2, z2, 1, 1, VERTEX, false); + idx4 = addVertex(x2, y2, z1, 1, 0, VERTEX, false); + if (stroke) { + addEdge(idx1, idx2, true, false); + addEdge(idx2, idx3, false, false); + addEdge(idx3, idx4, false, false); + addEdge(idx4, idx1, false, false); + closeEdge(idx4, idx1); + } + } + } + + // Adds the vertices that define an sphere, without duplicating + // any vertex or edge. + int[] addSphere(float r, int detailU, int detailV, + boolean fill, boolean stroke) { + int nind = 3 * detailU + (6 * detailU + 3) * (detailV - 2) + 3 * detailU; + int[] indices = new int[nind]; + + int vertCount = 0; + int indCount = 0; + int vert0, vert1; + + float u, v; + float du = 1.0f / (detailU); + float dv = 1.0f / (detailV); + + // Southern cap ------------------------------------------------------- + + // Adding multiple copies of the south pole vertex, each one with a + // different u coordinate, so the texture mapping is correct when + // making the first strip of triangles. + u = 1; v = 1; + for (int i = 0; i < detailU; i++) { + setNormal(0, 1, 0); + addVertex(0, r, 0, u , v, VERTEX, true); + u -= du; + } + vertCount = detailU; + vert0 = vertCount; + u = 1; v -= dv; + for (int i = 0; i < detailU; i++) { + setNormal(pg.sphereX[i], pg.sphereY[i], pg.sphereZ[i]); + addVertex(r*pg.sphereX[i], r*pg.sphereY[i], r*pg.sphereZ[i], u , v, + VERTEX, false); + u -= du; + } + vertCount += detailU; + vert1 = vertCount; + setNormal(pg.sphereX[0], pg.sphereY[0], pg.sphereZ[0]); + addVertex(r*pg.sphereX[0], r*pg.sphereY[0], r*pg.sphereZ[0], u, v, + VERTEX, false); + vertCount++; + + for (int i = 0; i < detailU; i++) { + int i1 = vert0 + i; + int i0 = vert0 + i - detailU; + + indices[3 * i + 0] = i1; + indices[3 * i + 1] = i0; + indices[3 * i + 2] = i1 + 1; + + addEdge(i0, i1, true, true); + addEdge(i1, i1 + 1, true, true); + } + indCount += 3 * detailU; + + // Middle rings ------------------------------------------------------- + + int offset = 0; + for (int j = 2; j < detailV; j++) { + offset += detailU; + vert0 = vertCount; + u = 1; v -= dv; + for (int i = 0; i < detailU; i++) { + int ioff = offset + i; + setNormal(pg.sphereX[ioff], pg.sphereY[ioff], pg.sphereZ[ioff]); + addVertex(r*pg.sphereX[ioff], r*pg.sphereY[ioff], r*pg.sphereZ[ioff], + u , v, VERTEX, false); + u -= du; + } + vertCount += detailU; + vert1 = vertCount; + setNormal(pg.sphereX[offset], pg.sphereY[offset], pg.sphereZ[offset]); + addVertex(r*pg.sphereX[offset], r*pg.sphereY[offset], r*pg.sphereZ[offset], + u, v, VERTEX, false); + vertCount++; + + for (int i = 0; i < detailU; i++) { + int i1 = vert0 + i; + int i0 = vert0 + i - detailU - 1; + + indices[indCount + 6 * i + 0] = i1; + indices[indCount + 6 * i + 1] = i0; + indices[indCount + 6 * i + 2] = i0 + 1; + + indices[indCount + 6 * i + 3] = i1; + indices[indCount + 6 * i + 4] = i0 + 1; + indices[indCount + 6 * i + 5] = i1 + 1; + + addEdge(i0, i1, true, true); + addEdge(i1, i1 + 1, true, true); + addEdge(i0 + 1, i1, true, true); + } + indCount += 6 * detailU; + indices[indCount + 0] = vert1; + indices[indCount + 1] = vert1 - detailU; + indices[indCount + 2] = vert1 - 1; + indCount += 3; + + addEdge(vert1 - detailU, vert1 - 1, true, true); + addEdge(vert1 - 1, vert1, true, true); + } + + // Northern cap ------------------------------------------------------- + + // Adding multiple copies of the north pole vertex, each one with a + // different u coordinate, so the texture mapping is correct when + // making the last strip of triangles. + u = 1; v = 0; + for (int i = 0; i < detailU; i++) { + setNormal(0, -1, 0); + addVertex(0, -r, 0, u , v, VERTEX, false); + u -= du; + } + vertCount += detailU; + + for (int i = 0; i < detailU; i++) { + int i0 = vert0 + i; + int i1 = vert0 + i + detailU + 1; + + indices[indCount + 3 * i + 0] = i1; + indices[indCount + 3 * i + 1] = i0; + indices[indCount + 3 * i + 2] = i0 + 1; + + addEdge(i0, i0 + 1, true, true); + addEdge(i0, i1, true, true); + } + indCount += 3 * detailU; + + return indices; + } + } + + + // Holds tessellated data for polygon, line and point geometry. + static protected class TessGeometry { + int renderMode; + PGraphicsOpenGL pg; + + // Tessellated polygon data + int polyVertexCount; + int firstPolyVertex; + int lastPolyVertex; + FloatBuffer polyVerticesBuffer; + IntBuffer polyColorsBuffer; + FloatBuffer polyNormalsBuffer; + FloatBuffer polyTexCoordsBuffer; + + // Polygon material properties (polyColors is used + // as the diffuse color when lighting is enabled) + IntBuffer polyAmbientBuffer; + IntBuffer polySpecularBuffer; + IntBuffer polyEmissiveBuffer; + FloatBuffer polyShininessBuffer; + + int polyIndexCount; + int firstPolyIndex; + int lastPolyIndex; + ShortBuffer polyIndicesBuffer; + IndexCache polyIndexCache = new IndexCache(); + + // Tessellated line data + int lineVertexCount; + int firstLineVertex; + int lastLineVertex; + FloatBuffer lineVerticesBuffer; + IntBuffer lineColorsBuffer; + FloatBuffer lineDirectionsBuffer; + + int lineIndexCount; + int firstLineIndex; + int lastLineIndex; + ShortBuffer lineIndicesBuffer; + IndexCache lineIndexCache = new IndexCache(); + + // Tessellated point data + int pointVertexCount; + int firstPointVertex; + int lastPointVertex; + FloatBuffer pointVerticesBuffer; + IntBuffer pointColorsBuffer; + FloatBuffer pointOffsetsBuffer; + + int pointIndexCount; + int firstPointIndex; + int lastPointIndex; + ShortBuffer pointIndicesBuffer; + IndexCache pointIndexCache = new IndexCache(); + + // Backing arrays + float[] polyVertices; + int[] polyColors; + float[] polyNormals; + float[] polyTexCoords; + int[] polyAmbient; + int[] polySpecular; + int[] polyEmissive; + float[] polyShininess; + short[] polyIndices; + float[] lineVertices; + int[] lineColors; + float[] lineDirections; + short[] lineIndices; + float[] pointVertices; + int[] pointColors; + float[] pointOffsets; + short[] pointIndices; + + TessGeometry(PGraphicsOpenGL pg, int mode) { + this.pg = pg; + renderMode = mode; + allocate(); + } + + // ----------------------------------------------------------------- + // + // Allocate/dispose + + void allocate() { + polyVertices = new float[4 * PGL.DEFAULT_TESS_VERTICES]; + polyColors = new int[PGL.DEFAULT_TESS_VERTICES]; + polyNormals = new float[3 * PGL.DEFAULT_TESS_VERTICES]; + polyTexCoords = new float[2 * PGL.DEFAULT_TESS_VERTICES]; + polyAmbient = new int[PGL.DEFAULT_TESS_VERTICES]; + polySpecular = new int[PGL.DEFAULT_TESS_VERTICES]; + polyEmissive = new int[PGL.DEFAULT_TESS_VERTICES]; + polyShininess = new float[PGL.DEFAULT_TESS_VERTICES]; + polyIndices = new short[PGL.DEFAULT_TESS_VERTICES]; + + lineVertices = new float[4 * PGL.DEFAULT_TESS_VERTICES]; + lineColors = new int[PGL.DEFAULT_TESS_VERTICES]; + lineDirections = new float[4 * PGL.DEFAULT_TESS_VERTICES]; + lineIndices = new short[PGL.DEFAULT_TESS_VERTICES]; + + pointVertices = new float[4 * PGL.DEFAULT_TESS_VERTICES]; + pointColors = new int[PGL.DEFAULT_TESS_VERTICES]; + pointOffsets = new float[2 * PGL.DEFAULT_TESS_VERTICES]; + pointIndices = new short[PGL.DEFAULT_TESS_VERTICES]; + + polyVerticesBuffer = PGL.allocateFloatBuffer(polyVertices); + polyColorsBuffer = PGL.allocateIntBuffer(polyColors); + polyNormalsBuffer = PGL.allocateFloatBuffer(polyNormals); + polyTexCoordsBuffer = PGL.allocateFloatBuffer(polyTexCoords); + polyAmbientBuffer = PGL.allocateIntBuffer(polyAmbient); + polySpecularBuffer = PGL.allocateIntBuffer(polySpecular); + polyEmissiveBuffer = PGL.allocateIntBuffer(polyEmissive); + polyShininessBuffer = PGL.allocateFloatBuffer(polyShininess); + polyIndicesBuffer = PGL.allocateShortBuffer(polyIndices); + + lineVerticesBuffer = PGL.allocateFloatBuffer(lineVertices); + lineColorsBuffer = PGL.allocateIntBuffer(lineColors); + lineDirectionsBuffer = PGL.allocateFloatBuffer(lineDirections); + lineIndicesBuffer = PGL.allocateShortBuffer(lineIndices); + + pointVerticesBuffer = PGL.allocateFloatBuffer(pointVertices); + pointColorsBuffer = PGL.allocateIntBuffer(pointColors); + pointOffsetsBuffer = PGL.allocateFloatBuffer(pointOffsets); + pointIndicesBuffer = PGL.allocateShortBuffer(pointIndices); + + clear(); + } + + void clear() { + firstPolyVertex = lastPolyVertex = polyVertexCount = 0; + firstPolyIndex = lastPolyIndex = polyIndexCount = 0; + + firstLineVertex = lastLineVertex = lineVertexCount = 0; + firstLineIndex = lastLineIndex = lineIndexCount = 0; + + firstPointVertex = lastPointVertex = pointVertexCount = 0; + firstPointIndex = lastPointIndex = pointIndexCount = 0; + + polyIndexCache.clear(); + lineIndexCache.clear(); + pointIndexCache.clear(); + } + + void polyVertexCheck() { + if (polyVertexCount == polyVertices.length / 4) { + int newSize = polyVertexCount << 1; + + expandPolyVertices(newSize); + expandPolyColors(newSize); + expandPolyNormals(newSize); + expandPolyTexCoords(newSize); + expandPolyAmbient(newSize); + expandPolySpecular(newSize); + expandPolyEmissive(newSize); + expandPolyShininess(newSize); + } + + firstPolyVertex = polyVertexCount; + polyVertexCount++; + lastPolyVertex = polyVertexCount - 1; + } + + void polyVertexCheck(int count) { + int oldSize = polyVertices.length / 4; + if (polyVertexCount + count > oldSize) { + int newSize = expandArraySize(oldSize, polyVertexCount + count); + + expandPolyVertices(newSize); + expandPolyColors(newSize); + expandPolyNormals(newSize); + expandPolyTexCoords(newSize); + expandPolyAmbient(newSize); + expandPolySpecular(newSize); + expandPolyEmissive(newSize); + expandPolyShininess(newSize); + } + + firstPolyVertex = polyVertexCount; + polyVertexCount += count; + lastPolyVertex = polyVertexCount - 1; + } + + void polyIndexCheck(int count) { + int oldSize = polyIndices.length; + if (polyIndexCount + count > oldSize) { + int newSize = expandArraySize(oldSize, polyIndexCount + count); + + expandPolyIndices(newSize); + } + + firstPolyIndex = polyIndexCount; + polyIndexCount += count; + lastPolyIndex = polyIndexCount - 1; + } + + void polyIndexCheck() { + if (polyIndexCount == polyIndices.length) { + int newSize = polyIndexCount << 1; + + expandPolyIndices(newSize); + } + + firstPolyIndex = polyIndexCount; + polyIndexCount++; + lastPolyIndex = polyIndexCount - 1; + } + + void lineVertexCheck(int count) { + int oldSize = lineVertices.length / 4; + if (lineVertexCount + count > oldSize) { + int newSize = expandArraySize(oldSize, lineVertexCount + count); + + expandLineVertices(newSize); + expandLineColors(newSize); + expandLineDirections(newSize); + } + + firstLineVertex = lineVertexCount; + lineVertexCount += count; + lastLineVertex = lineVertexCount - 1; + } + + void lineIndexCheck(int count) { + int oldSize = lineIndices.length; + if (lineIndexCount + count > oldSize) { + int newSize = expandArraySize(oldSize, lineIndexCount + count); + + expandLineIndices(newSize); + } + + firstLineIndex = lineIndexCount; + lineIndexCount += count; + lastLineIndex = lineIndexCount - 1; + } + + void pointVertexCheck(int count) { + int oldSize = pointVertices.length / 4; + if (pointVertexCount + count > oldSize) { + int newSize = expandArraySize(oldSize, pointVertexCount + count); + + expandPointVertices(newSize); + expandPointColors(newSize); + expandPointOffsets(newSize); + } + + firstPointVertex = pointVertexCount; + pointVertexCount += count; + lastPointVertex = pointVertexCount - 1; + } + + void pointIndexCheck(int count) { + int oldSize = pointIndices.length; + if (pointIndexCount + count > oldSize) { + int newSize = expandArraySize(oldSize, pointIndexCount + count); + + expandPointIndices(newSize); + } + + firstPointIndex = pointIndexCount; + pointIndexCount += count; + lastPointIndex = pointIndexCount - 1; + } + + // ----------------------------------------------------------------- + // + // Query + + boolean isFull() { + return PGL.FLUSH_VERTEX_COUNT <= polyVertexCount || + PGL.FLUSH_VERTEX_COUNT <= lineVertexCount || + PGL.FLUSH_VERTEX_COUNT <= pointVertexCount; + } + + void getPolyVertexMin(PVector v, int first, int last) { + for (int i = first; i <= last; i++) { + int index = 4 * i; + v.x = PApplet.min(v.x, polyVertices[index++]); + v.y = PApplet.min(v.y, polyVertices[index++]); + v.z = PApplet.min(v.z, polyVertices[index ]); + } + } + + void getLineVertexMin(PVector v, int first, int last) { + for (int i = first; i <= last; i++) { + int index = 4 * i; + v.x = PApplet.min(v.x, lineVertices[index++]); + v.y = PApplet.min(v.y, lineVertices[index++]); + v.z = PApplet.min(v.z, lineVertices[index ]); + } + } + + void getPointVertexMin(PVector v, int first, int last) { + for (int i = first; i <= last; i++) { + int index = 4 * i; + v.x = PApplet.min(v.x, pointVertices[index++]); + v.y = PApplet.min(v.y, pointVertices[index++]); + v.z = PApplet.min(v.z, pointVertices[index ]); + } + } + + void getPolyVertexMax(PVector v, int first, int last) { + for (int i = first; i <= last; i++) { + int index = 4 * i; + v.x = PApplet.max(v.x, polyVertices[index++]); + v.y = PApplet.max(v.y, polyVertices[index++]); + v.z = PApplet.max(v.z, polyVertices[index ]); + } + } + + void getLineVertexMax(PVector v, int first, int last) { + for (int i = first; i <= last; i++) { + int index = 4 * i; + v.x = PApplet.max(v.x, lineVertices[index++]); + v.y = PApplet.max(v.y, lineVertices[index++]); + v.z = PApplet.max(v.z, lineVertices[index ]); + } + } + + void getPointVertexMax(PVector v, int first, int last) { + for (int i = first; i <= last; i++) { + int index = 4 * i; + v.x = PApplet.max(v.x, pointVertices[index++]); + v.y = PApplet.max(v.y, pointVertices[index++]); + v.z = PApplet.max(v.z, pointVertices[index ]); + } + } + + int getPolyVertexSum(PVector v, int first, int last) { + for (int i = first; i <= last; i++) { + int index = 4 * i; + v.x += polyVertices[index++]; + v.y += polyVertices[index++]; + v.z += polyVertices[index ]; + } + return last - first + 1; + } + + int getLineVertexSum(PVector v, int first, int last) { + for (int i = first; i <= last; i++) { + int index = 4 * i; + v.x += lineVertices[index++]; + v.y += lineVertices[index++]; + v.z += lineVertices[index ]; + } + return last - first + 1; + } + + int getPointVertexSum(PVector v, int first, int last) { + for (int i = first; i <= last; i++) { + int index = 4 * i; + v.x += pointVertices[index++]; + v.y += pointVertices[index++]; + v.z += pointVertices[index ]; + } + return last - first + 1; + } + + // ----------------------------------------------------------------- + // + // Methods to prepare buffers for relative read/write operations + + protected void updatePolyVerticesBuffer() { + updatePolyVerticesBuffer(0, polyVertexCount); + } + + protected void updatePolyVerticesBuffer(int offset, int size) { + PGL.updateFloatBuffer(polyVerticesBuffer, polyVertices, + 4 * offset, 4 * size); + } + + protected void updatePolyColorsBuffer() { + updatePolyColorsBuffer(0, polyVertexCount); + } + + protected void updatePolyColorsBuffer(int offset, int size) { + PGL.updateIntBuffer(polyColorsBuffer, polyColors, offset, size); + } + + protected void updatePolyNormalsBuffer() { + updatePolyNormalsBuffer(0, polyVertexCount); + } + + protected void updatePolyNormalsBuffer(int offset, int size) { + PGL.updateFloatBuffer(polyNormalsBuffer, polyNormals, + 3 * offset, 3 * size); + } + + protected void updatePolyTexCoordsBuffer() { + updatePolyTexCoordsBuffer(0, polyVertexCount); + } + + protected void updatePolyTexCoordsBuffer(int offset, int size) { + PGL.updateFloatBuffer(polyTexCoordsBuffer, polyTexCoords, + 2 * offset, 2 * size); + } + + protected void updatePolyAmbientBuffer() { + updatePolyAmbientBuffer(0, polyVertexCount); + } + + protected void updatePolyAmbientBuffer(int offset, int size) { + PGL.updateIntBuffer(polyAmbientBuffer, polyAmbient, offset, size); + } + + protected void updatePolySpecularBuffer() { + updatePolySpecularBuffer(0, polyVertexCount); + } + + protected void updatePolySpecularBuffer(int offset, int size) { + PGL.updateIntBuffer(polySpecularBuffer, polySpecular, offset, size); + } + + protected void updatePolyEmissiveBuffer() { + updatePolyEmissiveBuffer(0, polyVertexCount); + } + + protected void updatePolyEmissiveBuffer(int offset, int size) { + PGL.updateIntBuffer(polyEmissiveBuffer, polyEmissive, offset, size); + } + + protected void updatePolyShininessBuffer() { + updatePolyShininessBuffer(0, polyVertexCount); + } + + protected void updatePolyShininessBuffer(int offset, int size) { + PGL.updateFloatBuffer(polyShininessBuffer, polyShininess, offset, size); + } + + protected void updatePolyIndicesBuffer() { + updatePolyIndicesBuffer(0, polyIndexCount); + } + + protected void updatePolyIndicesBuffer(int offset, int size) { + PGL.updateShortBuffer(polyIndicesBuffer, polyIndices, offset, size); + } + + protected void updateLineVerticesBuffer() { + updateLineVerticesBuffer(0, lineVertexCount); + } + + protected void updateLineVerticesBuffer(int offset, int size) { + PGL.updateFloatBuffer(lineVerticesBuffer, lineVertices, + 4 * offset, 4 * size); + } + + protected void updateLineColorsBuffer() { + updateLineColorsBuffer(0, lineVertexCount); + } + + protected void updateLineColorsBuffer(int offset, int size) { + PGL.updateIntBuffer(lineColorsBuffer, lineColors, offset, size); + } + + protected void updateLineDirectionsBuffer() { + updateLineDirectionsBuffer(0, lineVertexCount); + } + + protected void updateLineDirectionsBuffer(int offset, int size) { + PGL.updateFloatBuffer(lineDirectionsBuffer, lineDirections, + 4 * offset, 4 * size); + } + + protected void updateLineIndicesBuffer() { + updateLineIndicesBuffer(0, lineIndexCount); + } + + protected void updateLineIndicesBuffer(int offset, int size) { + PGL.updateShortBuffer(lineIndicesBuffer, lineIndices, offset, size); + } + + protected void updatePointVerticesBuffer() { + updatePointVerticesBuffer(0, pointVertexCount); + } + + protected void updatePointVerticesBuffer(int offset, int size) { + PGL.updateFloatBuffer(pointVerticesBuffer, pointVertices, + 4 * offset, 4 * size); + } + + protected void updatePointColorsBuffer() { + updatePointColorsBuffer(0, pointVertexCount); + } + + protected void updatePointColorsBuffer(int offset, int size) { + PGL.updateIntBuffer(pointColorsBuffer, pointColors, offset, size); + } + + protected void updatePointOffsetsBuffer() { + updatePointOffsetsBuffer(0, pointVertexCount); + } + + protected void updatePointOffsetsBuffer(int offset, int size) { + PGL.updateFloatBuffer(pointOffsetsBuffer, pointOffsets, + 2 * offset, 2 * size); + } + + protected void updatePointIndicesBuffer() { + updatePointIndicesBuffer(0, pointIndexCount); + } + + protected void updatePointIndicesBuffer(int offset, int size) { + PGL.updateShortBuffer(pointIndicesBuffer, pointIndices, offset, size); + } + + // ----------------------------------------------------------------- + // + // Expand arrays + + void expandPolyVertices(int n) { + float temp[] = new float[4 * n]; + PApplet.arrayCopy(polyVertices, 0, temp, 0, 4 * polyVertexCount); + polyVertices = temp; + polyVerticesBuffer = PGL.allocateFloatBuffer(polyVertices); + } + + void expandPolyColors(int n) { + int temp[] = new int[n]; + PApplet.arrayCopy(polyColors, 0, temp, 0, polyVertexCount); + polyColors = temp; + polyColorsBuffer = PGL.allocateIntBuffer(polyColors); + } + + void expandPolyNormals(int n) { + float temp[] = new float[3 * n]; + PApplet.arrayCopy(polyNormals, 0, temp, 0, 3 * polyVertexCount); + polyNormals = temp; + polyNormalsBuffer = PGL.allocateFloatBuffer(polyNormals); + } + + void expandPolyTexCoords(int n) { + float temp[] = new float[2 * n]; + PApplet.arrayCopy(polyTexCoords, 0, temp, 0, 2 * polyVertexCount); + polyTexCoords = temp; + polyTexCoordsBuffer = PGL.allocateFloatBuffer(polyTexCoords); + } + + void expandPolyAmbient(int n) { + int temp[] = new int[n]; + PApplet.arrayCopy(polyAmbient, 0, temp, 0, polyVertexCount); + polyAmbient = temp; + polyAmbientBuffer = PGL.allocateIntBuffer(polyAmbient); + } + + void expandPolySpecular(int n) { + int temp[] = new int[n]; + PApplet.arrayCopy(polySpecular, 0, temp, 0, polyVertexCount); + polySpecular = temp; + polySpecularBuffer = PGL.allocateIntBuffer(polySpecular); + } + + void expandPolyEmissive(int n) { + int temp[] = new int[n]; + PApplet.arrayCopy(polyEmissive, 0, temp, 0, polyVertexCount); + polyEmissive = temp; + polyEmissiveBuffer = PGL.allocateIntBuffer(polyEmissive); + } + + void expandPolyShininess(int n) { + float temp[] = new float[n]; + PApplet.arrayCopy(polyShininess, 0, temp, 0, polyVertexCount); + polyShininess = temp; + polyShininessBuffer = PGL.allocateFloatBuffer(polyShininess); + } + + void expandPolyIndices(int n) { + short temp[] = new short[n]; + PApplet.arrayCopy(polyIndices, 0, temp, 0, polyIndexCount); + polyIndices = temp; + polyIndicesBuffer = PGL.allocateShortBuffer(polyIndices); + } + + void expandLineVertices(int n) { + float temp[] = new float[4 * n]; + PApplet.arrayCopy(lineVertices, 0, temp, 0, 4 * lineVertexCount); + lineVertices = temp; + lineVerticesBuffer = PGL.allocateFloatBuffer(lineVertices); + } + + void expandLineColors(int n) { + int temp[] = new int[n]; + PApplet.arrayCopy(lineColors, 0, temp, 0, lineVertexCount); + lineColors = temp; + lineColorsBuffer = PGL.allocateIntBuffer(lineColors); + } + + void expandLineDirections(int n) { + float temp[] = new float[4 * n]; + PApplet.arrayCopy(lineDirections, 0, temp, 0, 4 * lineVertexCount); + lineDirections = temp; + lineDirectionsBuffer = PGL.allocateFloatBuffer(lineDirections); + } + + void expandLineIndices(int n) { + short temp[] = new short[n]; + PApplet.arrayCopy(lineIndices, 0, temp, 0, lineIndexCount); + lineIndices = temp; + lineIndicesBuffer = PGL.allocateShortBuffer(lineIndices); + } + + void expandPointVertices(int n) { + float temp[] = new float[4 * n]; + PApplet.arrayCopy(pointVertices, 0, temp, 0, 4 * pointVertexCount); + pointVertices = temp; + pointVerticesBuffer = PGL.allocateFloatBuffer(pointVertices); + } + + void expandPointColors(int n) { + int temp[] = new int[n]; + PApplet.arrayCopy(pointColors, 0, temp, 0, pointVertexCount); + pointColors = temp; + pointColorsBuffer = PGL.allocateIntBuffer(pointColors); + } + + void expandPointOffsets(int n) { + float temp[] = new float[2 * n]; + PApplet.arrayCopy(pointOffsets, 0, temp, 0, 2 * pointVertexCount); + pointOffsets = temp; + pointOffsetsBuffer = PGL.allocateFloatBuffer(pointOffsets); + } + + void expandPointIndices(int n) { + short temp[] = new short[n]; + PApplet.arrayCopy(pointIndices, 0, temp, 0, pointIndexCount); + pointIndices = temp; + pointIndicesBuffer = PGL.allocateShortBuffer(pointIndices); + } + + // ----------------------------------------------------------------- + // + // Trim arrays + + void trim() { + if (0 < polyVertexCount && polyVertexCount < polyVertices.length / 4) { + trimPolyVertices(); + trimPolyColors(); + trimPolyNormals(); + trimPolyTexCoords(); + trimPolyAmbient(); + trimPolySpecular(); + trimPolyEmissive(); + trimPolyShininess(); + } + + if (0 < polyIndexCount && polyIndexCount < polyIndices.length) { + trimPolyIndices(); + } + + if (0 < lineVertexCount && lineVertexCount < lineVertices.length / 4) { + trimLineVertices(); + trimLineColors(); + trimLineDirections(); + } + + if (0 < lineIndexCount && lineIndexCount < lineIndices.length) { + trimLineIndices(); + } + + if (0 < pointVertexCount && pointVertexCount < pointVertices.length / 4) { + trimPointVertices(); + trimPointColors(); + trimPointOffsets(); + } + + if (0 < pointIndexCount && pointIndexCount < pointIndices.length) { + trimPointIndices(); + } + } + + void trimPolyVertices() { + float temp[] = new float[4 * polyVertexCount]; + PApplet.arrayCopy(polyVertices, 0, temp, 0, 4 * polyVertexCount); + polyVertices = temp; + polyVerticesBuffer = PGL.allocateFloatBuffer(polyVertices); + } + + void trimPolyColors() { + int temp[] = new int[polyVertexCount]; + PApplet.arrayCopy(polyColors, 0, temp, 0, polyVertexCount); + polyColors = temp; + polyColorsBuffer = PGL.allocateIntBuffer(polyColors); + } + + void trimPolyNormals() { + float temp[] = new float[3 * polyVertexCount]; + PApplet.arrayCopy(polyNormals, 0, temp, 0, 3 * polyVertexCount); + polyNormals = temp; + polyNormalsBuffer = PGL.allocateFloatBuffer(polyNormals); + } + + void trimPolyTexCoords() { + float temp[] = new float[2 * polyVertexCount]; + PApplet.arrayCopy(polyTexCoords, 0, temp, 0, 2 * polyVertexCount); + polyTexCoords = temp; + polyTexCoordsBuffer = PGL.allocateFloatBuffer(polyTexCoords); + } + + void trimPolyAmbient() { + int temp[] = new int[polyVertexCount]; + PApplet.arrayCopy(polyAmbient, 0, temp, 0, polyVertexCount); + polyAmbient = temp; + polyAmbientBuffer = PGL.allocateIntBuffer(polyAmbient); + } + + void trimPolySpecular() { + int temp[] = new int[polyVertexCount]; + PApplet.arrayCopy(polySpecular, 0, temp, 0, polyVertexCount); + polySpecular = temp; + polySpecularBuffer = PGL.allocateIntBuffer(polySpecular); + } + + void trimPolyEmissive() { + int temp[] = new int[polyVertexCount]; + PApplet.arrayCopy(polyEmissive, 0, temp, 0, polyVertexCount); + polyEmissive = temp; + polyEmissiveBuffer = PGL.allocateIntBuffer(polyEmissive); + } + + void trimPolyShininess() { + float temp[] = new float[polyVertexCount]; + PApplet.arrayCopy(polyShininess, 0, temp, 0, polyVertexCount); + polyShininess = temp; + polyShininessBuffer = PGL.allocateFloatBuffer(polyShininess); + } + + void trimPolyIndices() { + short temp[] = new short[polyIndexCount]; + PApplet.arrayCopy(polyIndices, 0, temp, 0, polyIndexCount); + polyIndices = temp; + polyIndicesBuffer = PGL.allocateShortBuffer(polyIndices); + } + + void trimLineVertices() { + float temp[] = new float[4 * lineVertexCount]; + PApplet.arrayCopy(lineVertices, 0, temp, 0, 4 * lineVertexCount); + lineVertices = temp; + lineVerticesBuffer = PGL.allocateFloatBuffer(lineVertices); + } + + void trimLineColors() { + int temp[] = new int[lineVertexCount]; + PApplet.arrayCopy(lineColors, 0, temp, 0, lineVertexCount); + lineColors = temp; + lineColorsBuffer = PGL.allocateIntBuffer(lineColors); + } + + void trimLineDirections() { + float temp[] = new float[4 * lineVertexCount]; + PApplet.arrayCopy(lineDirections, 0, temp, 0, 4 * lineVertexCount); + lineDirections = temp; + lineDirectionsBuffer = PGL.allocateFloatBuffer(lineDirections); + } + + void trimLineIndices() { + short temp[] = new short[lineIndexCount]; + PApplet.arrayCopy(lineIndices, 0, temp, 0, lineIndexCount); + lineIndices = temp; + lineIndicesBuffer = PGL.allocateShortBuffer(lineIndices); + } + + void trimPointVertices() { + float temp[] = new float[4 * pointVertexCount]; + PApplet.arrayCopy(pointVertices, 0, temp, 0, 4 * pointVertexCount); + pointVertices = temp; + pointVerticesBuffer = PGL.allocateFloatBuffer(pointVertices); + } + + void trimPointColors() { + int temp[] = new int[pointVertexCount]; + PApplet.arrayCopy(pointColors, 0, temp, 0, pointVertexCount); + pointColors = temp; + pointColorsBuffer = PGL.allocateIntBuffer(pointColors); + } + + void trimPointOffsets() { + float temp[] = new float[2 * pointVertexCount]; + PApplet.arrayCopy(pointOffsets, 0, temp, 0, 2 * pointVertexCount); + pointOffsets = temp; + pointOffsetsBuffer = PGL.allocateFloatBuffer(pointOffsets); + } + + void trimPointIndices() { + short temp[] = new short[pointIndexCount]; + PApplet.arrayCopy(pointIndices, 0, temp, 0, pointIndexCount); + pointIndices = temp; + pointIndicesBuffer = PGL.allocateShortBuffer(pointIndices); + } + + // ----------------------------------------------------------------- + // + // Aggregation methods + + void incPolyIndices(int first, int last, int inc) { + for (int i = first; i <= last; i++) { + polyIndices[i] += inc; + } + } + + void incLineIndices(int first, int last, int inc) { + for (int i = first; i <= last; i++) { + lineIndices[i] += inc; + } + } + + void incPointIndices(int first, int last, int inc) { + for (int i = first; i <= last; i++) { + pointIndices[i] += inc; + } + } + + // ----------------------------------------------------------------- + // + // Normal calculation + + // Expects vertices in CW (left-handed) order. + void calcPolyNormal(int i0, int i1, int i2) { + int index; + + index = 4 * i0; + float x0 = polyVertices[index++]; + float y0 = polyVertices[index++]; + float z0 = polyVertices[index ]; + + index = 4 * i1; + float x1 = polyVertices[index++]; + float y1 = polyVertices[index++]; + float z1 = polyVertices[index ]; + + index = 4 * i2; + float x2 = polyVertices[index++]; + float y2 = polyVertices[index++]; + float z2 = polyVertices[index ]; + + float v12x = x2 - x1; + float v12y = y2 - y1; + float v12z = z2 - z1; + + float v10x = x0 - x1; + float v10y = y0 - y1; + float v10z = z0 - z1; + + float nx = v12y * v10z - v10y * v12z; + float ny = v12z * v10x - v10z * v12x; + float nz = v12x * v10y - v10x * v12y; + float d = PApplet.sqrt(nx * nx + ny * ny + nz * nz); + nx /= d; + ny /= d; + nz /= d; + + index = 3 * i0; + polyNormals[index++] = nx; + polyNormals[index++] = ny; + polyNormals[index ] = nz; + + index = 3 * i1; + polyNormals[index++] = nx; + polyNormals[index++] = ny; + polyNormals[index ] = nz; + + index = 3 * i2; + polyNormals[index++] = nx; + polyNormals[index++] = ny; + polyNormals[index ] = nz; + } + + // ----------------------------------------------------------------- + // + // Add point geometry + + // Sets point vertex with index tessIdx using the data from input vertex + // inIdx. + void setPointVertex(int tessIdx, InGeometry in, int inIdx) { + int index; + + index = 3 * inIdx; + float x = in.vertices[index++]; + float y = in.vertices[index++]; + float z = in.vertices[index ]; + + if (renderMode == IMMEDIATE && pg.flushMode == FLUSH_WHEN_FULL) { + PMatrix3D mm = pg.modelview; + + index = 4 * tessIdx; + pointVertices[index++] = x*mm.m00 + y*mm.m01 + z*mm.m02 + mm.m03; + pointVertices[index++] = x*mm.m10 + y*mm.m11 + z*mm.m12 + mm.m13; + pointVertices[index++] = x*mm.m20 + y*mm.m21 + z*mm.m22 + mm.m23; + pointVertices[index ] = x*mm.m30 + y*mm.m31 + z*mm.m32 + mm.m33; + } else { + index = 4 * tessIdx; + pointVertices[index++] = x; + pointVertices[index++] = y; + pointVertices[index++] = z; + pointVertices[index ] = 1; + } + + pointColors[tessIdx] = in.strokeColors[inIdx]; + } + + // ----------------------------------------------------------------- + // + // Add line geometry + + void setLineVertex(int tessIdx, float[] vertices, int inIdx0, int rgba) { + int index; + + index = 3 * inIdx0; + float x0 = vertices[index++]; + float y0 = vertices[index++]; + float z0 = vertices[index ]; + + if (renderMode == IMMEDIATE && pg.flushMode == FLUSH_WHEN_FULL) { + PMatrix3D mm = pg.modelview; + + index = 4 * tessIdx; + lineVertices[index++] = x0*mm.m00 + y0*mm.m01 + z0*mm.m02 + mm.m03; + lineVertices[index++] = x0*mm.m10 + y0*mm.m11 + z0*mm.m12 + mm.m13; + lineVertices[index++] = x0*mm.m20 + y0*mm.m21 + z0*mm.m22 + mm.m23; + lineVertices[index ] = x0*mm.m30 + y0*mm.m31 + z0*mm.m32 + mm.m33; + } else { + index = 4 * tessIdx; + lineVertices[index++] = x0; + lineVertices[index++] = y0; + lineVertices[index++] = z0; + lineVertices[index ] = 1; + } + + lineColors[tessIdx] = rgba; + index = 4 * tessIdx; + lineDirections[index++] = 0; + lineDirections[index++] = 0; + lineDirections[index++] = 0; + lineDirections[index ] = 0; + } + + // Sets line vertex with index tessIdx using the data from input vertices + // inIdx0 and inIdx1. + void setLineVertex(int tessIdx, float[] vertices, int inIdx0, int inIdx1, + int rgba, float weight) { + int index; + + index = 3 * inIdx0; + float x0 = vertices[index++]; + float y0 = vertices[index++]; + float z0 = vertices[index ]; + + index = 3 * inIdx1; + float x1 = vertices[index++]; + float y1 = vertices[index++]; + float z1 = vertices[index ]; + + float dx = x1 - x0; + float dy = y1 - y0; + float dz = z1 - z0; + + if (renderMode == IMMEDIATE && pg.flushMode == FLUSH_WHEN_FULL) { + PMatrix3D mm = pg.modelview; + + index = 4 * tessIdx; + lineVertices[index++] = x0*mm.m00 + y0*mm.m01 + z0*mm.m02 + mm.m03; + lineVertices[index++] = x0*mm.m10 + y0*mm.m11 + z0*mm.m12 + mm.m13; + lineVertices[index++] = x0*mm.m20 + y0*mm.m21 + z0*mm.m22 + mm.m23; + lineVertices[index ] = x0*mm.m30 + y0*mm.m31 + z0*mm.m32 + mm.m33; + + index = 4 * tessIdx; + lineDirections[index++] = dx*mm.m00 + dy*mm.m01 + dz*mm.m02; + lineDirections[index++] = dx*mm.m10 + dy*mm.m11 + dz*mm.m12; + lineDirections[index ] = dx*mm.m20 + dy*mm.m21 + dz*mm.m22; + } else { + index = 4 * tessIdx; + lineVertices[index++] = x0; + lineVertices[index++] = y0; + lineVertices[index++] = z0; + lineVertices[index ] = 1; + + index = 4 * tessIdx; + lineDirections[index++] = dx; + lineDirections[index++] = dy; + lineDirections[index ] = dz; + } + + lineColors[tessIdx] = rgba; + lineDirections[4 * tessIdx + 3] = weight; + } + + // ----------------------------------------------------------------- + // + // Add poly geometry + + void addPolyVertex(float x, float y, float z, + int rgba, + float nx, float ny, float nz, + float u, float v, + int am, int sp, int em, float shine, + boolean clampXY) { + polyVertexCheck(); + int tessIdx = polyVertexCount - 1; + setPolyVertex(tessIdx, x, y, z, + rgba, + nx, ny, nz, + u, v, + am, sp, em, shine, clampXY); + } + + void setPolyVertex(int tessIdx, float x, float y, float z, int rgba, + boolean clampXY) { + setPolyVertex(tessIdx, x, y, z, + rgba, + 0, 0, 1, + 0, 0, + 0, 0, 0, 0, clampXY); + } + + void setPolyVertex(int tessIdx, float x, float y, float z, + int rgba, + float nx, float ny, float nz, + float u, float v, + int am, int sp, int em, float shine, + boolean clampXY) { + int index; + + if (renderMode == IMMEDIATE && pg.flushMode == FLUSH_WHEN_FULL) { + PMatrix3D mm = pg.modelview; + PMatrix3D nm = pg.modelviewInv; + + index = 4 * tessIdx; + if (clampXY) { + // ceil emulates the behavior of JAVA2D + polyVertices[index++] = + PApplet.ceil(x*mm.m00 + y*mm.m01 + z*mm.m02 + mm.m03); + polyVertices[index++] = + PApplet.ceil(x*mm.m10 + y*mm.m11 + z*mm.m12 + mm.m13); + } else { + polyVertices[index++] = x*mm.m00 + y*mm.m01 + z*mm.m02 + mm.m03; + polyVertices[index++] = x*mm.m10 + y*mm.m11 + z*mm.m12 + mm.m13; + } + polyVertices[index++] = x*mm.m20 + y*mm.m21 + z*mm.m22 + mm.m23; + polyVertices[index ] = x*mm.m30 + y*mm.m31 + z*mm.m32 + mm.m33; + + index = 3 * tessIdx; + polyNormals[index++] = nx*nm.m00 + ny*nm.m10 + nz*nm.m20; + polyNormals[index++] = nx*nm.m01 + ny*nm.m11 + nz*nm.m21; + polyNormals[index ] = nx*nm.m02 + ny*nm.m12 + nz*nm.m22; + } else { + index = 4 * tessIdx; + polyVertices[index++] = x; + polyVertices[index++] = y; + polyVertices[index++] = z; + polyVertices[index ] = 1; + + index = 3 * tessIdx; + polyNormals[index++] = nx; + polyNormals[index++] = ny; + polyNormals[index ] = nz; + } + + polyColors[tessIdx] = rgba; + + index = 2 * tessIdx; + polyTexCoords[index++] = u; + polyTexCoords[index ] = v; + + polyAmbient[tessIdx] = am; + polySpecular[tessIdx] = sp; + polyEmissive[tessIdx] = em; + polyShininess[tessIdx] = shine; + } + + void addPolyVertices(InGeometry in, boolean clampXY) { + addPolyVertices(in, 0, in.vertexCount - 1, clampXY); + } + + void addPolyVertex(InGeometry in, int i, boolean clampXY) { + addPolyVertices(in, i, i, clampXY); + } + + void addPolyVertices(InGeometry in, int i0, int i1, boolean clampXY) { + int index; + int nvert = i1 - i0 + 1; + + polyVertexCheck(nvert); + + if (renderMode == IMMEDIATE && pg.flushMode == FLUSH_WHEN_FULL) { + PMatrix3D mm = pg.modelview; + PMatrix3D nm = pg.modelviewInv; + + for (int i = 0; i < nvert; i++) { + int inIdx = i0 + i; + int tessIdx = firstPolyVertex + i; + + index = 3 * inIdx; + float x = in.vertices[index++]; + float y = in.vertices[index++]; + float z = in.vertices[index ]; + + index = 3 * inIdx; + float nx = in.normals[index++]; + float ny = in.normals[index++]; + float nz = in.normals[index ]; + + index = 4 * tessIdx; + if (clampXY) { + // ceil emulates the behavior of JAVA2D + polyVertices[index++] = + PApplet.ceil(x*mm.m00 + y*mm.m01 + z*mm.m02 + mm.m03); + polyVertices[index++] = + PApplet.ceil(x*mm.m10 + y*mm.m11 + z*mm.m12 + mm.m13); + } else { + polyVertices[index++] = x*mm.m00 + y*mm.m01 + z*mm.m02 + mm.m03; + polyVertices[index++] = x*mm.m10 + y*mm.m11 + z*mm.m12 + mm.m13; + } + polyVertices[index++] = x*mm.m20 + y*mm.m21 + z*mm.m22 + mm.m23; + polyVertices[index ] = x*mm.m30 + y*mm.m31 + z*mm.m32 + mm.m33; + + index = 3 * tessIdx; + polyNormals[index++] = nx*nm.m00 + ny*nm.m10 + nz*nm.m20; + polyNormals[index++] = nx*nm.m01 + ny*nm.m11 + nz*nm.m21; + polyNormals[index ] = nx*nm.m02 + ny*nm.m12 + nz*nm.m22; + } + } else { + if (nvert <= PGL.MIN_ARRAYCOPY_SIZE) { + // Copying elements one by one instead of using arrayCopy is more + // efficient for few vertices... + for (int i = 0; i < nvert; i++) { + int inIdx = i0 + i; + int tessIdx = firstPolyVertex + i; + + index = 3 * inIdx; + float x = in.vertices[index++]; + float y = in.vertices[index++]; + float z = in.vertices[index ]; + + index = 3 * inIdx; + float nx = in.normals[index++]; + float ny = in.normals[index++]; + float nz = in.normals[index ]; + + index = 4 * tessIdx; + polyVertices[index++] = x; + polyVertices[index++] = y; + polyVertices[index++] = z; + polyVertices[index ] = 1; + + index = 3 * tessIdx; + polyNormals[index++] = nx; + polyNormals[index++] = ny; + polyNormals[index ] = nz; + } + } else { + for (int i = 0; i < nvert; i++) { + int inIdx = i0 + i; + int tessIdx = firstPolyVertex + i; + PApplet.arrayCopy(in.vertices, 3 * inIdx, + polyVertices, 4 * tessIdx, 3); + polyVertices[4 * tessIdx + 3] = 1; + } + PApplet.arrayCopy(in.normals, 3 * i0, + polyNormals, 3 * firstPolyVertex, 3 * nvert); + } + } + + if (nvert <= PGL.MIN_ARRAYCOPY_SIZE) { + for (int i = 0; i < nvert; i++) { + int inIdx = i0 + i; + int tessIdx = firstPolyVertex + i; + + index = 2 * inIdx; + float u = in.texcoords[index++]; + float v = in.texcoords[index ]; + + polyColors[tessIdx] = in.colors[inIdx]; + + index = 2 * tessIdx; + polyTexCoords[index++] = u; + polyTexCoords[index ] = v; + + polyAmbient[tessIdx] = in.ambient[inIdx]; + polySpecular[tessIdx] = in.specular[inIdx]; + polyEmissive[tessIdx] = in.emissive[inIdx]; + polyShininess[tessIdx] = in.shininess[inIdx]; + } + } else { + PApplet.arrayCopy(in.colors, i0, + polyColors, firstPolyVertex, nvert); + PApplet.arrayCopy(in.texcoords, 2 * i0, + polyTexCoords, 2 * firstPolyVertex, 2 * nvert); + PApplet.arrayCopy(in.ambient, i0, + polyAmbient, firstPolyVertex, nvert); + PApplet.arrayCopy(in.specular, i0, + polySpecular, firstPolyVertex, nvert); + PApplet.arrayCopy(in.emissive, i0, + polyEmissive, firstPolyVertex, nvert); + PApplet.arrayCopy(in.shininess, i0, + polyShininess, firstPolyVertex, nvert); + } + } + + // ----------------------------------------------------------------- + // + // Matrix transformations + + void applyMatrixOnPolyGeometry(PMatrix tr, int first, int last) { + if (tr instanceof PMatrix2D) { + applyMatrixOnPolyGeometry((PMatrix2D) tr, first, last); + } else if (tr instanceof PMatrix3D) { + applyMatrixOnPolyGeometry((PMatrix3D) tr, first, last); + } + } + + void applyMatrixOnLineGeometry(PMatrix tr, int first, int last) { + if (tr instanceof PMatrix2D) { + applyMatrixOnLineGeometry((PMatrix2D) tr, first, last); + } else if (tr instanceof PMatrix3D) { + applyMatrixOnLineGeometry((PMatrix3D) tr, first, last); + } + } + + void applyMatrixOnPointGeometry(PMatrix tr, int first, int last) { + if (tr instanceof PMatrix2D) { + applyMatrixOnPointGeometry((PMatrix2D) tr, first, last); + } else if (tr instanceof PMatrix3D) { + applyMatrixOnPointGeometry((PMatrix3D) tr, first, last); + } + } + + void applyMatrixOnPolyGeometry(PMatrix2D tr, int first, int last) { + if (first < last) { + int index; + + for (int i = first; i <= last; i++) { + index = 4 * i; + float x = polyVertices[index++]; + float y = polyVertices[index ]; + + index = 3 * i; + float nx = polyNormals[index++]; + float ny = polyNormals[index ]; + + index = 4 * i; + polyVertices[index++] = x*tr.m00 + y*tr.m01 + tr.m02; + polyVertices[index ] = x*tr.m10 + y*tr.m11 + tr.m12; + + index = 3 * i; + polyNormals[index++] = nx*tr.m00 + ny*tr.m01; + polyNormals[index ] = nx*tr.m10 + ny*tr.m11; + } + } + } + + void applyMatrixOnLineGeometry(PMatrix2D tr, int first, int last) { + if (first < last) { + int index; + + for (int i = first; i <= last; i++) { + index = 4 * i; + float x = lineVertices[index++]; + float y = lineVertices[index ]; + + index = 4 * i; + float xa = lineDirections[index++]; + float ya = lineDirections[index ]; + + float dx = xa - x; + float dy = ya - y; + + index = 4 * i; + lineVertices[index++] = x*tr.m00 + y*tr.m01 + tr.m02; + lineVertices[index ] = x*tr.m10 + y*tr.m11 + tr.m12; + + index = 4 * i; + lineDirections[index++] = dx*tr.m00 + dy*tr.m01; + lineDirections[index ] = dx*tr.m10 + dy*tr.m11; + } + } + } + + void applyMatrixOnPointGeometry(PMatrix2D tr, int first, int last) { + if (first < last) { + int index; + + for (int i = first; i <= last; i++) { + index = 4 * i; + float x = pointVertices[index++]; + float y = pointVertices[index ]; + + index = 4 * i; + pointVertices[index++] = x*tr.m00 + y*tr.m01 + tr.m02; + pointVertices[index ] = x*tr.m10 + y*tr.m11 + tr.m12; + } + } + } + + void applyMatrixOnPolyGeometry(PMatrix3D tr, int first, int last) { + if (first < last) { + int index; + + for (int i = first; i <= last; i++) { + index = 4 * i; + float x = polyVertices[index++]; + float y = polyVertices[index++]; + float z = polyVertices[index++]; + float w = polyVertices[index ]; + + index = 3 * i; + float nx = polyNormals[index++]; + float ny = polyNormals[index++]; + float nz = polyNormals[index ]; + + index = 4 * i; + polyVertices[index++] = x*tr.m00 + y*tr.m01 + z*tr.m02 + w*tr.m03; + polyVertices[index++] = x*tr.m10 + y*tr.m11 + z*tr.m12 + w*tr.m13; + polyVertices[index++] = x*tr.m20 + y*tr.m21 + z*tr.m22 + w*tr.m23; + polyVertices[index ] = x*tr.m30 + y*tr.m31 + z*tr.m32 + w*tr.m33; + + index = 3 * i; + polyNormals[index++] = nx*tr.m00 + ny*tr.m01 + nz*tr.m02; + polyNormals[index++] = nx*tr.m10 + ny*tr.m11 + nz*tr.m12; + polyNormals[index ] = nx*tr.m20 + ny*tr.m21 + nz*tr.m22; + } + } + } + + void applyMatrixOnLineGeometry(PMatrix3D tr, int first, int last) { + if (first < last) { + int index; + + for (int i = first; i <= last; i++) { + index = 4 * i; + float x = lineVertices[index++]; + float y = lineVertices[index++]; + float z = lineVertices[index++]; + float w = lineVertices[index ]; + + index = 4 * i; + float xa = lineDirections[index++]; + float ya = lineDirections[index++]; + float za = lineDirections[index ]; + + float dx = xa - x; + float dy = ya - y; + float dz = za - z; + + index = 4 * i; + lineVertices[index++] = x*tr.m00 + y*tr.m01 + z*tr.m02 + w*tr.m03; + lineVertices[index++] = x*tr.m10 + y*tr.m11 + z*tr.m12 + w*tr.m13; + lineVertices[index++] = x*tr.m20 + y*tr.m21 + z*tr.m22 + w*tr.m23; + lineVertices[index ] = x*tr.m30 + y*tr.m31 + z*tr.m32 + w*tr.m33; + + index = 4 * i; + lineDirections[index++] = dx*tr.m00 + dy*tr.m01 + dz*tr.m02; + lineDirections[index++] = dx*tr.m10 + dy*tr.m11 + dz*tr.m12; + lineDirections[index ] = dx*tr.m20 + dy*tr.m21 + dz*tr.m22; + } + } + } + + void applyMatrixOnPointGeometry(PMatrix3D tr, int first, int last) { + if (first < last) { + int index; + + for (int i = first; i <= last; i++) { + index = 4 * i; + float x = pointVertices[index++]; + float y = pointVertices[index++]; + float z = pointVertices[index++]; + float w = pointVertices[index ]; + + index = 4 * i; + pointVertices[index++] = x*tr.m00 + y*tr.m01 + z*tr.m02 + w*tr.m03; + pointVertices[index++] = x*tr.m10 + y*tr.m11 + z*tr.m12 + w*tr.m13; + pointVertices[index++] = x*tr.m20 + y*tr.m21 + z*tr.m22 + w*tr.m23; + pointVertices[index ] = x*tr.m30 + y*tr.m31 + z*tr.m32 + w*tr.m33; + } + } + } + } + + // Generates tessellated geometry given a batch of input vertices. + static protected class Tessellator { + InGeometry in; + TessGeometry tess; + TexCache texCache; + PImage prevTexImage; + PImage newTexImage; + int firstTexIndex; + int firstTexCache; + + PGL.Tessellator gluTess; + TessellatorCallback callback; + + boolean fill; + boolean stroke; + int strokeColor; + float strokeWeight; + int strokeJoin; + int strokeCap; + boolean accurate2DStrokes; + + PMatrix transform; + float transformScale; + boolean is2D, is3D; + protected PGraphicsOpenGL pg; + + int[] rawIndices; + int rawSize; + int[] dupIndices; + int dupCount; + + int firstPolyIndexCache; + int lastPolyIndexCache; + int firstLineIndexCache; + int lastLineIndexCache; + int firstPointIndexCache; + int lastPointIndexCache; + + // Accessor arrays to get the geometry data needed to tessellate the + // strokes, it can point to either the input geometry, or the internal + // path vertices generated in the polygon discretization. + float[] strokeVertices; + int[] strokeColors; + float[] strokeWeights; + + // Path vertex data that results from discretizing a polygon (i.e.: turning + // bezier, quadratic, and curve vertices into "regular" vertices). + int pathVertexCount; + float[] pathVertices; + int[] pathColors; + float[] pathWeights; + int beginPath; + + public Tessellator() { + rawIndices = new int[512]; + accurate2DStrokes = true; + transform = null; + is2D = false; + is3D = true; + } + + void initGluTess() { + if (gluTess == null) { + callback = new TessellatorCallback(); + gluTess = pg.pgl.createTessellator(callback); + } + } + + void setInGeometry(InGeometry in) { + this.in = in; + + firstPolyIndexCache = -1; + lastPolyIndexCache = -1; + firstLineIndexCache = -1; + lastLineIndexCache = -1; + firstPointIndexCache = -1; + lastPointIndexCache = -1; + } + + void setTessGeometry(TessGeometry tess) { + this.tess = tess; + } + + void setFill(boolean fill) { + this.fill = fill; + } + + void setTexCache(TexCache texCache, PImage newTexImage) { + this.texCache = texCache; + this.newTexImage = newTexImage; + } + + void setStroke(boolean stroke) { + this.stroke = stroke; + } + + void setStrokeColor(int color) { + this.strokeColor = PGL.javaToNativeARGB(color); + } + + void setStrokeWeight(float weight) { + this.strokeWeight = weight; + } + + void setStrokeCap(int strokeCap) { + this.strokeCap = strokeCap; + } + + void setStrokeJoin(int strokeJoin) { + this.strokeJoin = strokeJoin; + } + + void setAccurate2DStrokes(boolean accurate) { + this.accurate2DStrokes = accurate; + } + + protected void setRenderer(PGraphicsOpenGL pg) { + this.pg = pg; + } + + void set3D(boolean value) { + if (value) { + this.is2D = false; + this.is3D = true; + } else { + this.is2D = true; + this.is3D = false; + } + } + + void setTransform(PMatrix transform) { + this.transform = transform; + transformScale = -1; + } + + void resetCurveVertexCount() { + pg.curveVertexCount = 0; + } + + // ----------------------------------------------------------------- + // + // Point tessellation + + void tessellatePoints() { + if (strokeCap == ROUND) { + tessellateRoundPoints(); + } else { + tessellateSquarePoints(); + } + } + + void tessellateRoundPoints() { + int nInVert = in.vertexCount; + if (stroke && 1 <= nInVert) { + // Each point generates a separate triangle fan. + // The number of triangles of each fan depends on the + // stroke weight of the point. + int nPtVert = + PApplet.min(MAX_POINT_ACCURACY, PApplet.max(MIN_POINT_ACCURACY, + (int) (TWO_PI * strokeWeight / + POINT_ACCURACY_FACTOR))) + 1; + if (PGL.MAX_VERTEX_INDEX1 <= nPtVert) { + throw new RuntimeException("Error in point tessellation."); + } + updateTex(); + int nvertTot = nPtVert * nInVert; + int nindTot = 3 * (nPtVert - 1) * nInVert; + if (is3D) { + tessellateRoundPoints3D(nvertTot, nindTot, nPtVert); + } else if (is2D) { + beginNoTex(); + tessellateRoundPoints2D(nvertTot, nindTot, nPtVert); + endNoTex(); + } + } + } + + void tessellateRoundPoints3D(int nvertTot, int nindTot, int nPtVert) { + int perim = nPtVert - 1; + tess.pointVertexCheck(nvertTot); + tess.pointIndexCheck(nindTot); + int vertIdx = tess.firstPointVertex; + int attribIdx = tess.firstPointVertex; + int indIdx = tess.firstPointIndex; + IndexCache cache = tess.pointIndexCache; + int index = in.renderMode == RETAINED ? cache.addNew() : cache.getLast(); + firstPointIndexCache = index; + for (int i = 0; i < in.vertexCount; i++) { + // Creating the triangle fan for each input vertex. + + int count = cache.vertexCount[index]; + if (PGL.MAX_VERTEX_INDEX1 <= count + nPtVert) { + // We need to start a new index block for this point. + index = cache.addNew(); + count = 0; + } + + // All the tessellated vertices are identical to the center point + for (int k = 0; k < nPtVert; k++) { + tess.setPointVertex(vertIdx, in, i); + vertIdx++; + } + + // The attributes for each tessellated vertex are the displacement along + // the circle perimeter. The point shader will read these attributes and + // displace the vertices in screen coordinates so the circles are always + // camera facing (bilboards) + tess.pointOffsets[2 * attribIdx + 0] = 0; + tess.pointOffsets[2 * attribIdx + 1] = 0; + attribIdx++; + float val = 0; + float inc = (float) SINCOS_LENGTH / perim; + for (int k = 0; k < perim; k++) { + tess.pointOffsets[2 * attribIdx + 0] = + 0.5f * cosLUT[(int) val] * strokeWeight; + tess.pointOffsets[2 * attribIdx + 1] = + 0.5f * sinLUT[(int) val] * strokeWeight; + val = (val + inc) % SINCOS_LENGTH; + attribIdx++; + } + + // Adding vert0 to take into account the triangles of all + // the preceding points. + for (int k = 1; k < nPtVert - 1; k++) { + tess.pointIndices[indIdx++] = (short) (count + 0); + tess.pointIndices[indIdx++] = (short) (count + k); + tess.pointIndices[indIdx++] = (short) (count + k + 1); + } + // Final triangle between the last and first point: + tess.pointIndices[indIdx++] = (short) (count + 0); + tess.pointIndices[indIdx++] = (short) (count + 1); + tess.pointIndices[indIdx++] = (short) (count + nPtVert - 1); + + cache.incCounts(index, 3 * (nPtVert - 1), nPtVert); + } + lastPointIndexCache = index; + } + + void tessellateRoundPoints2D(int nvertTot, int nindTot, int nPtVert) { + int perim = nPtVert - 1; + tess.polyVertexCheck(nvertTot); + tess.polyIndexCheck(nindTot); + int vertIdx = tess.firstPolyVertex; + int indIdx = tess.firstPolyIndex; + IndexCache cache = tess.polyIndexCache; + int index = in.renderMode == RETAINED ? cache.addNew() : cache.getLast(); + firstPointIndexCache = index; + if (firstPolyIndexCache == -1) firstPolyIndexCache = index; // If the geometry has no fill, needs the first poly index. + for (int i = 0; i < in.vertexCount; i++) { + int count = cache.vertexCount[index]; + if (PGL.MAX_VERTEX_INDEX1 <= count + nPtVert) { + // We need to start a new index block for this point. + index = cache.addNew(); + count = 0; + } + + float x0 = in.vertices[3 * i + 0]; + float y0 = in.vertices[3 * i + 1]; + int rgba = in.strokeColors[i]; + + float val = 0; + float inc = (float) SINCOS_LENGTH / perim; + tess.setPolyVertex(vertIdx, x0, y0, 0, rgba, false); + vertIdx++; + for (int k = 0; k < perim; k++) { + tess.setPolyVertex(vertIdx, + x0 + 0.5f * cosLUT[(int) val] * strokeWeight, + y0 + 0.5f * sinLUT[(int) val] * strokeWeight, + 0, rgba, false); + vertIdx++; + val = (val + inc) % SINCOS_LENGTH; + } + + // Adding vert0 to take into account the triangles of all + // the preceding points. + for (int k = 1; k < nPtVert - 1; k++) { + tess.polyIndices[indIdx++] = (short) (count + 0); + tess.polyIndices[indIdx++] = (short) (count + k); + tess.polyIndices[indIdx++] = (short) (count + k + 1); + } + // Final triangle between the last and first point: + tess.polyIndices[indIdx++] = (short) (count + 0); + tess.polyIndices[indIdx++] = (short) (count + 1); + tess.polyIndices[indIdx++] = (short) (count + nPtVert - 1); + + cache.incCounts(index, 3 * (nPtVert - 1), nPtVert); + } + lastPointIndexCache = lastPolyIndexCache = index; + } + + void tessellateSquarePoints() { + int nInVert = in.vertexCount; + if (stroke && 1 <= nInVert) { + updateTex(); + int quadCount = nInVert; // Each point generates a separate quad. + // Each quad is formed by 5 vertices, the center one + // is the input vertex, and the other 4 define the + // corners (so, a triangle fan again). + int nvertTot = 5 * quadCount; + // So the quad is formed by 4 triangles, each requires + // 3 indices. + int nindTot = 12 * quadCount; + if (is3D) { + tessellateSquarePoints3D(nvertTot, nindTot); + } else if (is2D) { + beginNoTex(); + tessellateSquarePoints2D(nvertTot, nindTot); + endNoTex(); + } + } + } + + void tessellateSquarePoints3D(int nvertTot, int nindTot) { + tess.pointVertexCheck(nvertTot); + tess.pointIndexCheck(nindTot); + int vertIdx = tess.firstPointVertex; + int attribIdx = tess.firstPointVertex; + int indIdx = tess.firstPointIndex; + IndexCache cache = tess.pointIndexCache; + int index = in.renderMode == RETAINED ? cache.addNew() : cache.getLast(); + firstPointIndexCache = index; + for (int i = 0; i < in.vertexCount; i++) { + int nvert = 5; + int count = cache.vertexCount[index]; + if (PGL.MAX_VERTEX_INDEX1 <= count + nvert) { + // We need to start a new index block for this point. + index = cache.addNew(); + count = 0; + } + + for (int k = 0; k < nvert; k++) { + tess.setPointVertex(vertIdx, in, i); + vertIdx++; + } + + // The attributes for each tessellated vertex are the displacement along + // the quad corners. The point shader will read these attributes and + // displace the vertices in screen coordinates so the quads are always + // camera facing (bilboards) + tess.pointOffsets[2 * attribIdx + 0] = 0; + tess.pointOffsets[2 * attribIdx + 1] = 0; + attribIdx++; + for (int k = 0; k < 4; k++) { + tess.pointOffsets[2 * attribIdx + 0] = + 0.5f * QUAD_POINT_SIGNS[k][0] * strokeWeight; + tess.pointOffsets[2 * attribIdx + 1] = + 0.5f * QUAD_POINT_SIGNS[k][1] * strokeWeight; + attribIdx++; + } + + // Adding firstVert to take into account the triangles of all + // the preceding points. + for (int k = 1; k < nvert - 1; k++) { + tess.pointIndices[indIdx++] = (short) (count + 0); + tess.pointIndices[indIdx++] = (short) (count + k); + tess.pointIndices[indIdx++] = (short) (count + k + 1); + } + // Final triangle between the last and first point: + tess.pointIndices[indIdx++] = (short) (count + 0); + tess.pointIndices[indIdx++] = (short) (count + 1); + tess.pointIndices[indIdx++] = (short) (count + nvert - 1); + + cache.incCounts(index, 12, 5); + } + lastPointIndexCache = index; + } + + void tessellateSquarePoints2D(int nvertTot, int nindTot) { + tess.polyVertexCheck(nvertTot); + tess.polyIndexCheck(nindTot); + boolean clamp = clampSquarePoints2D(); + int vertIdx = tess.firstPolyVertex; + int indIdx = tess.firstPolyIndex; + IndexCache cache = tess.polyIndexCache; + int index = in.renderMode == RETAINED ? cache.addNew() : cache.getLast(); + firstPointIndexCache = index; + if (firstPolyIndexCache == -1) firstPolyIndexCache = index; // If the geometry has no fill, needs the first poly index. + for (int i = 0; i < in.vertexCount; i++) { + int nvert = 5; + int count = cache.vertexCount[index]; + if (PGL.MAX_VERTEX_INDEX1 <= count + nvert) { + // We need to start a new index block for this point. + index = cache.addNew(); + count = 0; + } + + float x0 = in.vertices[3 * i + 0]; + float y0 = in.vertices[3 * i + 1]; + int rgba = in.strokeColors[i]; + + tess.setPolyVertex(vertIdx, x0, y0, 0, rgba, clamp); + vertIdx++; + for (int k = 0; k < nvert - 1; k++) { + tess.setPolyVertex(vertIdx, + x0 + 0.5f * QUAD_POINT_SIGNS[k][0] * strokeWeight, + y0 + 0.5f * QUAD_POINT_SIGNS[k][1] * strokeWeight, + 0, rgba, clamp); + vertIdx++; + } + + for (int k = 1; k < nvert - 1; k++) { + tess.polyIndices[indIdx++] = (short) (count + 0); + tess.polyIndices[indIdx++] = (short) (count + k); + tess.polyIndices[indIdx++] = (short) (count + k + 1); + } + // Final triangle between the last and first point: + tess.polyIndices[indIdx++] = (short) (count + 0); + tess.polyIndices[indIdx++] = (short) (count + 1); + tess.polyIndices[indIdx++] = (short) (count + nvert - 1); + + cache.incCounts(index, 12, 5); + } + lastPointIndexCache = lastPolyIndexCache = index; + } + + boolean clamp2D() { + return is2D && tess.renderMode == IMMEDIATE && + zero(pg.modelview.m01) && zero(pg.modelview.m10); + } + + boolean clampSquarePoints2D() { + return clamp2D(); + } + + // ----------------------------------------------------------------- + // + // Line tessellation + + void tessellateLines() { + int nInVert = in.vertexCount; + if (stroke && 2 <= nInVert) { + strokeVertices = in.vertices; + strokeColors = in.strokeColors; + strokeWeights = in.strokeWeights; + updateTex(); + int lineCount = nInVert / 2; // Each individual line is formed by two consecutive input vertices. + if (is3D) { + tessellateLines3D(lineCount); + } else if (is2D) { + beginNoTex(); // Line geometry in 2D are stored in the poly array next to the fill triangles, but w/out textures. + tessellateLines2D(lineCount); + endNoTex(); + } + } + } + + void tessellateLines3D(int lineCount) { + // Lines are made up of 4 vertices defining the quad. + int nvert = lineCount * 4; + // Each stroke line has 4 vertices, defining 2 triangles, which + // require 3 indices to specify their connectivities. + int nind = lineCount * 2 * 3; + + tess.lineVertexCheck(nvert); + tess.lineIndexCheck(nind); + int index = in.renderMode == RETAINED ? tess.lineIndexCache.addNew() : + tess.lineIndexCache.getLast(); + firstLineIndexCache = index; + for (int ln = 0; ln < lineCount; ln++) { + int i0 = 2 * ln + 0; + int i1 = 2 * ln + 1; + index = addLineSegment3D(i0, i1, index, null, false); + } + lastLineIndexCache = index; + } + + void tessellateLines2D(int lineCount) { + int nvert = lineCount * 4; + int nind = lineCount * 2 * 3; + + if (noCapsJoins(nvert)) { + tess.polyVertexCheck(nvert); + tess.polyIndexCheck(nind); + int index = in.renderMode == RETAINED ? tess.polyIndexCache.addNew() : + tess.polyIndexCache.getLast(); + firstLineIndexCache = index; + if (firstPolyIndexCache == -1) firstPolyIndexCache = index; // If the geometry has no fill, needs the first poly index. + boolean clamp = clampLines2D(lineCount); + for (int ln = 0; ln < lineCount; ln++) { + int i0 = 2 * ln + 0; + int i1 = 2 * ln + 1; + index = addLineSegment2D(i0, i1, index, false, clamp); + } + lastLineIndexCache = lastPolyIndexCache = index; + } else { // full stroking algorithm + LinePath path = new LinePath(LinePath.WIND_NON_ZERO); + for (int ln = 0; ln < lineCount; ln++) { + int i0 = 2 * ln + 0; + int i1 = 2 * ln + 1; + path.moveTo(in.vertices[3 * i0 + 0], in.vertices[3 * i0 + 1], + in.strokeColors[i0]); + path.lineTo(in.vertices[3 * i1 + 0], in.vertices[3 * i1 + 1], + in.strokeColors[i1]); + } + tessellateLinePath(path); + } + } + + boolean clampLines2D(int lineCount) { + boolean res = clamp2D(); + if (res) { + for (int ln = 0; ln < lineCount; ln++) { + int i0 = 2 * ln + 0; + int i1 = 2 * ln + 1; + res = segmentIsAxisAligned(i0, i1); + if (!res) break; + } + } + return res; + } + + void tessellateLineStrip() { + int nInVert = in.vertexCount; + if (stroke && 2 <= nInVert) { + strokeVertices = in.vertices; + strokeColors = in.strokeColors; + strokeWeights = in.strokeWeights; + updateTex(); + int lineCount = nInVert - 1; + if (is3D) { + tessellateLineStrip3D(lineCount); + } else if (is2D) { + beginNoTex(); + tessellateLineStrip2D(lineCount); + endNoTex(); + } + } + } + + void tessellateLineStrip3D(int lineCount) { + int nBevelTr = noCapsJoins() ? 0 : (lineCount - 1); + int nvert = lineCount * 4 + nBevelTr; + int nind = lineCount * 2 * 3 + nBevelTr * 2 * 3; + + tess.lineVertexCheck(nvert); + tess.lineIndexCheck(nind); + int index = in.renderMode == RETAINED ? tess.lineIndexCache.addNew() : + tess.lineIndexCache.getLast(); + firstLineIndexCache = index; + int i0 = 0; + short[] lastInd = {-1, -1}; + for (int ln = 0; ln < lineCount; ln++) { + int i1 = ln + 1; + if (0 < nBevelTr) { + index = addLineSegment3D(i0, i1, index, lastInd, false); + } else { + index = addLineSegment3D(i0, i1, index, null, false); + } + i0 = i1; + } + lastLineIndexCache = index; + } + + void tessellateLineStrip2D(int lineCount) { + int nvert = lineCount * 4; + int nind = lineCount * 2 * 3; + + if (noCapsJoins(nvert)) { + tess.polyVertexCheck(nvert); + tess.polyIndexCheck(nind); + int index = in.renderMode == RETAINED ? tess.polyIndexCache.addNew() : + tess.polyIndexCache.getLast(); + firstLineIndexCache = index; + if (firstPolyIndexCache == -1) firstPolyIndexCache = index; // If the geometry has no fill, needs the first poly index. + int i0 = 0; + boolean clamp = clampLineStrip2D(lineCount); + for (int ln = 0; ln < lineCount; ln++) { + int i1 = ln + 1; + index = addLineSegment2D(i0, i1, index, false, clamp); + i0 = i1; + } + lastLineIndexCache = lastPolyIndexCache = index; + } else { // full stroking algorithm + LinePath path = new LinePath(LinePath.WIND_NON_ZERO); + path.moveTo(in.vertices[0], in.vertices[1], in.strokeColors[0]); + for (int ln = 0; ln < lineCount; ln++) { + int i1 = ln + 1; + path.lineTo(in.vertices[3 * i1 + 0], in.vertices[3 * i1 + 1], + in.strokeColors[i1]); + } + tessellateLinePath(path); + } + } + + boolean clampLineStrip2D(int lineCount) { + boolean res = clamp2D(); + if (res) { + for (int ln = 0; ln < lineCount; ln++) { + res = segmentIsAxisAligned(0, ln + 1); + if (!res) break; + } + } + return res; + } + + void tessellateLineLoop() { + int nInVert = in.vertexCount; + if (stroke && 2 <= nInVert) { + strokeVertices = in.vertices; + strokeColors = in.strokeColors; + strokeWeights = in.strokeWeights; + updateTex(); + int lineCount = nInVert; + if (is3D) { + tessellateLineLoop3D(lineCount); + } else if (is2D) { + beginNoTex(); + tessellateLineLoop2D(lineCount); + endNoTex(); + } + } + } + + void tessellateLineLoop3D(int lineCount) { + int nBevelTr = noCapsJoins() ? 0 : lineCount; + int nvert = lineCount * 4 + nBevelTr; + int nind = lineCount * 2 * 3 + nBevelTr * 2 * 3; + + tess.lineVertexCheck(nvert); + tess.lineIndexCheck(nind); + int index = in.renderMode == RETAINED ? tess.lineIndexCache.addNew() : + tess.lineIndexCache.getLast(); + firstLineIndexCache = index; + int i0 = 0; + short[] lastInd = {-1, -1}; + short firstInd = -1; + for (int ln = 0; ln < lineCount - 1; ln++) { + int i1 = ln + 1; + if (0 < nBevelTr) { + index = addLineSegment3D(i0, i1, index, lastInd, false); + if (ln == 0) firstInd = (short)(lastInd[0] - 2); + } else { + index = addLineSegment3D(i0, i1, index, null, false); + } + i0 = i1; + } + index = addLineSegment3D(0, in.vertexCount - 1, index, lastInd, false); + if (0 < nBevelTr) { + index = addBevel3D(0, index, lastInd, firstInd, false); + } + lastLineIndexCache = index; + } + + void tessellateLineLoop2D(int lineCount) { + int nvert = lineCount * 4; + int nind = lineCount * 2 * 3; + + if (noCapsJoins(nvert)) { + tess.polyVertexCheck(nvert); + tess.polyIndexCheck(nind); + int index = in.renderMode == RETAINED ? tess.polyIndexCache.addNew() : + tess.polyIndexCache.getLast(); + firstLineIndexCache = index; + if (firstPolyIndexCache == -1) firstPolyIndexCache = index; // If the geometry has no fill, needs the first poly index. + int i0 = 0; + boolean clamp = clampLineLoop2D(lineCount); + for (int ln = 0; ln < lineCount - 1; ln++) { + int i1 = ln + 1; + index = addLineSegment2D(i0, i1, index, false, clamp); + i0 = i1; + } + index = addLineSegment2D(0, in.vertexCount - 1, index, false, clamp); + lastLineIndexCache = lastPolyIndexCache = index; + } else { // full stroking algorithm + LinePath path = new LinePath(LinePath.WIND_NON_ZERO); + path.moveTo(in.vertices[0], in.vertices[1], in.strokeColors[0]); + for (int ln = 0; ln < lineCount - 1; ln++) { + int i1 = ln + 1; + path.lineTo(in.vertices[3 * i1 + 0], in.vertices[3 * i1 + 1], + in.strokeColors[i1]); + } + path.closePath(); + tessellateLinePath(path); + } + } + + boolean clampLineLoop2D(int lineCount) { + boolean res = clamp2D(); + if (res) { + for (int ln = 0; ln < lineCount; ln++) { + res = segmentIsAxisAligned(0, ln + 1); + if (!res) break; + } + } + return res; + } + + void tessellateEdges() { + if (stroke) { + if (in.edgeCount == 0) return; + strokeVertices = in.vertices; + strokeColors = in.strokeColors; + strokeWeights = in.strokeWeights; + if (is3D) { + tessellateEdges3D(); + } else if (is2D) { + beginNoTex(); + tessellateEdges2D(); + endNoTex(); + } + } + } + + void tessellateEdges3D() { + boolean bevel = !noCapsJoins(); + int nInVert = in.getNumEdgeVertices(bevel); + int nInInd = in.getNumEdgeIndices(bevel); + + tess.lineVertexCheck(nInVert); + tess.lineIndexCheck(nInInd); + int index = in.renderMode == RETAINED ? tess.lineIndexCache.addNew() : + tess.lineIndexCache.getLast(); + firstLineIndexCache = index; + short[] lastInd = {-1, -1}; + short firstInd = -1; + for (int i = 0; i <= in.edgeCount - 1; i++) { + int[] edge = in.edges[i]; + int i0 = edge[0]; + int i1 = edge[1]; + if (bevel) { + if (edge[2] == EDGE_CLOSE) { + index = addBevel3D(edge[1], index, lastInd, firstInd, false); + lastInd[0] = lastInd[1] = -1; // No join with next line segment. + } else { + index = addLineSegment3D(i0, i1, index, lastInd, false); + if (edge[2] == EDGE_START) firstInd = (short)(lastInd[0] - 2); + if (edge[2] == EDGE_STOP || edge[2] == EDGE_SINGLE) { + lastInd[0] = lastInd[1] = -1; // No join with next line segment. + } + } + } else if (edge[2] != EDGE_CLOSE) { + index = addLineSegment3D(i0, i1, index, null, false); + } + } + lastLineIndexCache = index; + } + + void tessellateEdges2D() { + int nInVert = in.getNumEdgeVertices(false); + if (noCapsJoins(nInVert)) { + int nInInd = in.getNumEdgeIndices(false); + + tess.polyVertexCheck(nInVert); + tess.polyIndexCheck(nInInd); + int index = in.renderMode == RETAINED ? tess.polyIndexCache.addNew() : + tess.polyIndexCache.getLast(); + firstLineIndexCache = index; + if (firstPolyIndexCache == -1) firstPolyIndexCache = index; // If the geometry has no fill, needs the first poly index. + boolean clamp = clampEdges2D(); + for (int i = 0; i <= in.edgeCount - 1; i++) { + int[] edge = in.edges[i]; + if (edge[2] == EDGE_CLOSE) continue; // ignoring edge closures when not doing caps or joins. + int i0 = edge[0]; + int i1 = edge[1]; + index = addLineSegment2D(i0, i1, index, false, clamp); + } + lastLineIndexCache = lastPolyIndexCache = index; + } else { // full stroking algorithm + LinePath path = new LinePath(LinePath.WIND_NON_ZERO); + for (int i = 0; i <= in.edgeCount - 1; i++) { + int[] edge = in.edges[i]; + int i0 = edge[0]; + int i1 = edge[1]; + switch (edge[2]) { + case EDGE_MIDDLE: + path.lineTo(strokeVertices[3 * i1 + 0], strokeVertices[3 * i1 + 1], + strokeColors[i1]); + break; + case EDGE_START: + path.moveTo(strokeVertices[3 * i0 + 0], strokeVertices[3 * i0 + 1], + strokeColors[i0]); + path.lineTo(strokeVertices[3 * i1 + 0], strokeVertices[3 * i1 + 1], + strokeColors[i1]); + break; + case EDGE_STOP: + path.lineTo(strokeVertices[3 * i1 + 0], strokeVertices[3 * i1 + 1], + strokeColors[i1]); + path.moveTo(strokeVertices[3 * i1 + 0], strokeVertices[3 * i1 + 1], + strokeColors[i1]); + break; + case EDGE_SINGLE: + path.moveTo(strokeVertices[3 * i0 + 0], strokeVertices[3 * i0 + 1], + strokeColors[i0]); + path.lineTo(strokeVertices[3 * i1 + 0], strokeVertices[3 * i1 + 1], + strokeColors[i1]); + path.moveTo(strokeVertices[3 * i1 + 0], strokeVertices[3 * i1 + 1], + strokeColors[i1]); + break; + case EDGE_CLOSE: + path.closePath(); + break; + } + } + tessellateLinePath(path); + } + } + + boolean clampEdges2D() { + boolean res = clamp2D(); + if (res) { + for (int i = 0; i <= in.edgeCount - 1; i++) { + int[] edge = in.edges[i]; + if (edge[2] == EDGE_CLOSE) continue; + int i0 = edge[0]; + int i1 = edge[1]; + res = segmentIsAxisAligned(strokeVertices, i0, i1); + if (!res) break; + } + } + return res; + } + + // Adding the data that defines a quad starting at vertex i0 and + // ending at i1. + int addLineSegment3D(int i0, int i1, int index, short[] lastInd, + boolean constStroke) { + IndexCache cache = tess.lineIndexCache; + int count = cache.vertexCount[index]; + boolean addBevel = lastInd != null && -1 < lastInd[0] && -1 < lastInd[1]; + boolean newCache = false; + if (PGL.MAX_VERTEX_INDEX1 <= count + 4 + (addBevel ? 1 : 0)) { + // We need to start a new index block for this line. + index = cache.addNew(); + count = 0; + newCache = true; + } + int iidx = cache.indexOffset[index] + cache.indexCount[index]; + int vidx = cache.vertexOffset[index] + cache.vertexCount[index]; + int color, color0; + float weight; + + color0 = color = constStroke ? strokeColor : strokeColors[i0]; + weight = constStroke ? strokeWeight : strokeWeights[i0]; + weight *= transformScale(); + + tess.setLineVertex(vidx++, strokeVertices, i0, i1, color, +weight/2); + tess.lineIndices[iidx++] = (short) (count + 0); + + tess.setLineVertex(vidx++, strokeVertices, i0, i1, color, -weight/2); + tess.lineIndices[iidx++] = (short) (count + 1); + + color = constStroke ? strokeColor : strokeColors[i1]; + weight = constStroke ? strokeWeight : strokeWeights[i1]; + weight *= transformScale(); + + tess.setLineVertex(vidx++, strokeVertices, i1, i0, color, -weight/2); + tess.lineIndices[iidx++] = (short) (count + 2); + + // Starting a new triangle re-using prev vertices. + tess.lineIndices[iidx++] = (short) (count + 2); + tess.lineIndices[iidx++] = (short) (count + 1); + + tess.setLineVertex(vidx++, strokeVertices, i1, i0, color, +weight/2); + tess.lineIndices[iidx++] = (short) (count + 3); + + cache.incCounts(index, 6, 4); + + if (lastInd != null) { + if (-1 < lastInd[0] && -1 < lastInd[1]) { + // Adding bevel triangles + tess.setLineVertex(vidx, strokeVertices, i0, color0); + + if (newCache) { + PGraphics.showWarning(TOO_LONG_STROKE_PATH_ERROR); + + // TODO: Fix this situation, the vertices from the previous cache + // block should be copied in the newly created one. + tess.lineIndices[iidx++] = (short) (count + 4); + tess.lineIndices[iidx++] = (short) (count + 0); + tess.lineIndices[iidx++] = (short) (count + 0); + + tess.lineIndices[iidx++] = (short) (count + 4); + tess.lineIndices[iidx++] = (short) (count + 1); + tess.lineIndices[iidx ] = (short) (count + 1); + } else { + tess.lineIndices[iidx++] = (short) (count + 4); + tess.lineIndices[iidx++] = lastInd[0]; + tess.lineIndices[iidx++] = (short) (count + 0); + + tess.lineIndices[iidx++] = (short) (count + 4); + tess.lineIndices[iidx++] = lastInd[1]; + tess.lineIndices[iidx ] = (short) (count + 1); + } + + cache.incCounts(index, 6, 1); + } + + // Vertices for next bevel + lastInd[0] = (short) (count + 2); + lastInd[1] = (short) (count + 3); + } + return index; + } + + int addBevel3D(int i0, int index, short[] lastInd, short firstInd, + boolean constStroke) { + IndexCache cache = tess.lineIndexCache; + int count = cache.vertexCount[index]; + boolean addBevel = lastInd != null && -1 < lastInd[0] && -1 < lastInd[1]; + boolean newCache = false; + if (PGL.MAX_VERTEX_INDEX1 <= count + (addBevel ? 1 : 0)) { + // We need to start a new index block for this line. + index = cache.addNew(); + count = 0; + newCache = true; + } + int iidx = cache.indexOffset[index] + cache.indexCount[index]; + int vidx = cache.vertexOffset[index] + cache.vertexCount[index]; + int color0 = constStroke ? strokeColor : strokeColors[i0]; + + if (lastInd != null) { + if (-1 < lastInd[0] && -1 < lastInd[1]) { + tess.setLineVertex(vidx, strokeVertices, i0, color0); + + if (newCache) { + PGraphics.showWarning(TOO_LONG_STROKE_PATH_ERROR); + + // TODO: Fix this situation, the vertices from the previous cache + // block should be copied in the newly created one. +// tess.lineIndices[iidx++] = (short) (count + 4); +// tess.lineIndices[iidx++] = (short) (count + 0); +// tess.lineIndices[iidx++] = (short) (count + 0); +// +// tess.lineIndices[iidx++] = (short) (count + 4); +// tess.lineIndices[iidx++] = (short) (count + 1); +// tess.lineIndices[iidx ] = (short) (count + 1); + } else { + tess.lineIndices[iidx++] = (short) (count + 0); + tess.lineIndices[iidx++] = lastInd[0]; + tess.lineIndices[iidx++] = (short) (firstInd + 0); + + tess.lineIndices[iidx++] = (short) (count + 0); + tess.lineIndices[iidx++] = lastInd[1]; + tess.lineIndices[iidx ] = (short) (firstInd + 1); + } + + cache.incCounts(index, 6, 1); + } + } + + return index; + } + + // Adding the data that defines a quad starting at vertex i0 and + // ending at i1, in the case of pure 2D renderers (line geometry + // is added to the poly arrays). + int addLineSegment2D(int i0, int i1, int index, + boolean constStroke, boolean clamp) { + IndexCache cache = tess.polyIndexCache; + int count = cache.vertexCount[index]; + if (PGL.MAX_VERTEX_INDEX1 <= count + 4) { + // We need to start a new index block for this line. + index = cache.addNew(); + count = 0; + } + int iidx = cache.indexOffset[index] + cache.indexCount[index]; + int vidx = cache.vertexOffset[index] + cache.vertexCount[index]; + + int color = constStroke ? strokeColor : strokeColors[i0]; + float weight = constStroke ? strokeWeight : strokeWeights[i0]; + if (subPixelStroke(weight)) clamp = false; + + float x0 = strokeVertices[3 * i0 + 0]; + float y0 = strokeVertices[3 * i0 + 1]; + + float x1 = strokeVertices[3 * i1 + 0]; + float y1 = strokeVertices[3 * i1 + 1]; + + // Calculating direction and normal of the line. + float dirx = x1 - x0; + float diry = y1 - y0; + float llen = PApplet.sqrt(dirx * dirx + diry * diry); + float normx = 0, normy = 0; + float dirdx = 0, dirdy = 0; + if (nonZero(llen)) { + normx = -diry / llen; + normy = +dirx / llen; + + // Displacement along the direction of the line to force rounding to next + // integer and so making sure that no pixels are missing, some relevant + // links: + // http://stackoverflow.com/questions/10040961/opengl-pixel-perfect-2d-drawing + // http://msdn.microsoft.com/en-us/library/dd374282(VS.85) + dirdx = (dirx / llen) * PApplet.min(0.75f, weight/2); + dirdy = (diry / llen) * PApplet.min(0.75f, weight/2); + } + + float normdx = normx * weight/2; + float normdy = normy * weight/2; + + tess.setPolyVertex(vidx++, x0 + normdx - dirdx, y0 + normdy - dirdy, + 0, color, clamp); + tess.polyIndices[iidx++] = (short) (count + 0); + + tess.setPolyVertex(vidx++, x0 - normdx - dirdx, y0 - normdy - dirdy, + 0, color, clamp); + tess.polyIndices[iidx++] = (short) (count + 1); + + if (clamp) { + // Check for degeneracy due to coordinate clamping + float xac = tess.polyVertices[4 * (vidx - 2) + 0]; + float yac = tess.polyVertices[4 * (vidx - 2) + 1]; + float xbc = tess.polyVertices[4 * (vidx - 1) + 0]; + float ybc = tess.polyVertices[4 * (vidx - 1) + 1]; + if (same(xac, xbc) && same(yac, ybc)) { + unclampLine2D(vidx - 2, x0 + normdx - dirdx, y0 + normdy - dirdy); + unclampLine2D(vidx - 1, x0 - normdx - dirdx, y0 - normdy - dirdy); + } + } + + if (!constStroke) { + color = strokeColors[i1]; + weight = strokeWeights[i1]; + normdx = normx * weight/2; + normdy = normy * weight/2; + if (subPixelStroke(weight)) clamp = false; + } + + tess.setPolyVertex(vidx++, x1 - normdx + dirdx, y1 - normdy + dirdy, + 0, color, clamp); + tess.polyIndices[iidx++] = (short) (count + 2); + + // Starting a new triangle re-using prev vertices. + tess.polyIndices[iidx++] = (short) (count + 2); + tess.polyIndices[iidx++] = (short) (count + 0); + + tess.setPolyVertex(vidx++, x1 + normdx + dirdx, y1 + normdy + dirdy, + 0, color, clamp); + tess.polyIndices[iidx++] = (short) (count + 3); + + if (clamp) { + // Check for degeneracy due to coordinate clamping + float xac = tess.polyVertices[4 * (vidx - 2) + 0]; + float yac = tess.polyVertices[4 * (vidx - 2) + 1]; + float xbc = tess.polyVertices[4 * (vidx - 1) + 0]; + float ybc = tess.polyVertices[4 * (vidx - 1) + 1]; + if (same(xac, xbc) && same(yac, ybc)) { + unclampLine2D(vidx - 2, x1 - normdx + dirdx, y1 - normdy + dirdy); + unclampLine2D(vidx - 1, x1 + normdx + dirdx, y1 + normdy + dirdy); + } + } + + cache.incCounts(index, 6, 4); + return index; + } + + void unclampLine2D(int tessIdx, float x, float y) { + PMatrix3D mm = pg.modelview; + int index = 4 * tessIdx; + tess.polyVertices[index++] = x*mm.m00 + y*mm.m01 + mm.m03; + tess.polyVertices[index++] = x*mm.m10 + y*mm.m11 + mm.m13; + } + + boolean noCapsJoins(int nInVert) { + if (!accurate2DStrokes) { + return true; + } else if (PGL.MAX_CAPS_JOINS_LENGTH <= nInVert) { + // The line path is too long, so it could make the GLU tess + // to run out of memory, so full caps and joins are disabled. + return true; + } else { + return noCapsJoins(); + } + } + + boolean subPixelStroke(float weight) { + float sw = transformScale() * weight; + return PApplet.abs(sw - (int)sw) > 0; + } + + boolean noCapsJoins() { + // The stroke weight is scaled so it corresponds to the current + // "zoom level" being applied on the geometry due to scaling: + return tess.renderMode == IMMEDIATE && + transformScale() * strokeWeight < PGL.MIN_CAPS_JOINS_WEIGHT; + } + + float transformScale() { + if (-1 < transformScale) return transformScale; + + // Volumetric scaling factor that is associated to the current + // transformation matrix, which is given by the absolute value of its + // determinant: + float factor = 1; + + if (transform != null) { + if (transform instanceof PMatrix2D) { + PMatrix2D tr = (PMatrix2D)transform; + float areaScaleFactor = Math.abs(tr.m00 * tr.m11 - tr.m01 * tr.m10); + factor = (float) Math.sqrt(areaScaleFactor); + } else if (transform instanceof PMatrix3D) { + PMatrix3D tr = (PMatrix3D)transform; + float volumeScaleFactor = + Math.abs(tr.m00 * (tr.m11 * tr.m22 - tr.m12 * tr.m21) + + tr.m01 * (tr.m12 * tr.m20 - tr.m10 * tr.m22) + + tr.m02 * (tr.m10 * tr.m21 - tr.m11 * tr.m20)); + factor = (float) Math.pow(volumeScaleFactor, 1.0f / 3.0f); + } + } + + return transformScale = factor; + } + + boolean segmentIsAxisAligned(int i0, int i1) { + return zero(in.vertices[3 * i0 + 0] - in.vertices[3 * i1 + 0]) || + zero(in.vertices[3 * i0 + 1] - in.vertices[3 * i1 + 1]); + } + + boolean segmentIsAxisAligned(float[] vertices, int i0, int i1) { + return zero(vertices[3 * i0 + 0] - vertices[3 * i1 + 0]) || + zero(vertices[3 * i0 + 1] - vertices[3 * i1 + 1]); + } + + // ----------------------------------------------------------------- + // + // Polygon primitives tessellation + + void tessellateTriangles() { + beginTex(); + int nTri = in.vertexCount / 3; + if (fill && 1 <= nTri) { + int nInInd = 3 * nTri; + setRawSize(nInInd); + int idx = 0; + boolean clamp = clampTriangles(); + for (int i = 0; i < 3 * nTri; i++) { + rawIndices[idx++] = i; + } + splitRawIndices(clamp); + } + endTex(); + tessellateEdges(); + } + + boolean clampTriangles() { + boolean res = clamp2D(); + if (res) { + int nTri = in.vertexCount / 3; + for (int i = 0; i < nTri; i++) { + int i0 = 3 * i + 0; + int i1 = 3 * i + 1; + int i2 = 3 * i + 2; + int count = 0; + if (segmentIsAxisAligned(i0, i1)) count++; + if (segmentIsAxisAligned(i0, i2)) count++; + if (segmentIsAxisAligned(i1, i2)) count++; + res = 1 < count; + if (!res) break; + } + } + return res; + } + + void tessellateTriangles(int[] indices) { + beginTex(); + int nInVert = in.vertexCount; + if (fill && 3 <= nInVert) { + int nInInd = indices.length; + setRawSize(nInInd); + PApplet.arrayCopy(indices, rawIndices, nInInd); + boolean clamp = clampTriangles(indices); + splitRawIndices(clamp); + } + endTex(); + tessellateEdges(); + } + + boolean clampTriangles(int[] indices) { + boolean res = clamp2D(); + if (res) { + int nTri = indices.length; + for (int i = 0; i < nTri; i++) { + int i0 = indices[3 * i + 0]; + int i1 = indices[3 * i + 1]; + int i2 = indices[3 * i + 2]; + int count = 0; + if (segmentIsAxisAligned(i0, i1)) count++; + if (segmentIsAxisAligned(i0, i2)) count++; + if (segmentIsAxisAligned(i1, i2)) count++; + res = 1 < count; + if (!res) break; + } + } + return res; + } + + void tessellateTriangleFan() { + beginTex(); + int nInVert = in.vertexCount; + if (fill && 3 <= nInVert) { + int nInInd = 3 * (nInVert - 2); + setRawSize(nInInd); + int idx = 0; + boolean clamp = clampTriangleFan(); + for (int i = 1; i < in.vertexCount - 1; i++) { + rawIndices[idx++] = 0; + rawIndices[idx++] = i; + rawIndices[idx++] = i + 1; + } + splitRawIndices(clamp); + } + endTex(); + tessellateEdges(); + } + + boolean clampTriangleFan() { + boolean res = clamp2D(); + if (res) { + for (int i = 1; i < in.vertexCount - 1; i++) { + int i0 = 0; + int i1 = i; + int i2 = i + 1; + int count = 0; + if (segmentIsAxisAligned(i0, i1)) count++; + if (segmentIsAxisAligned(i0, i2)) count++; + if (segmentIsAxisAligned(i1, i2)) count++; + res = 1 < count; + if (!res) break; + } + } + return res; + } + + void tessellateTriangleStrip() { + beginTex(); + int nInVert = in.vertexCount; + if (fill && 3 <= nInVert) { + int nInInd = 3 * (nInVert - 2); + setRawSize(nInInd); + int idx = 0; + boolean clamp = clampTriangleStrip(); + for (int i = 1; i < in.vertexCount - 1; i++) { + rawIndices[idx++] = i; + if (i % 2 == 0) { + rawIndices[idx++] = i - 1; + rawIndices[idx++] = i + 1; + } else { + rawIndices[idx++] = i + 1; + rawIndices[idx++] = i - 1; + } + } + splitRawIndices(clamp); + } + endTex(); + tessellateEdges(); + } + + boolean clampTriangleStrip() { + boolean res = clamp2D(); + if (res) { + for (int i = 1; i < in.vertexCount - 1; i++) { + int i0 = i; + int i1, i2; + if (i % 2 == 0) { + i1 = i - 1; + i2 = i + 1; + } else { + i1 = i + 1; + i2 = i - 1; + } + int count = 0; + if (segmentIsAxisAligned(i0, i1)) count++; + if (segmentIsAxisAligned(i0, i2)) count++; + if (segmentIsAxisAligned(i1, i2)) count++; + res = 1 < count; + if (!res) break; + } + } + return res; + } + + void tessellateQuads() { + beginTex(); + int quadCount = in.vertexCount / 4; + if (fill && 1 <= quadCount) { + int nInInd = 6 * quadCount; + setRawSize(nInInd); + int idx = 0; + boolean clamp = clampQuads(quadCount); + for (int qd = 0; qd < quadCount; qd++) { + int i0 = 4 * qd + 0; + int i1 = 4 * qd + 1; + int i2 = 4 * qd + 2; + int i3 = 4 * qd + 3; + + rawIndices[idx++] = i0; + rawIndices[idx++] = i1; + rawIndices[idx++] = i2; + + rawIndices[idx++] = i2; + rawIndices[idx++] = i3; + rawIndices[idx++] = i0; + } + splitRawIndices(clamp); + } + endTex(); + tessellateEdges(); + } + + boolean clampQuads(int quadCount) { + boolean res = clamp2D(); + if (res) { + for (int qd = 0; qd < quadCount; qd++) { + int i0 = 4 * qd + 0; + int i1 = 4 * qd + 1; + int i2 = 4 * qd + 2; + int i3 = 4 * qd + 3; + res = segmentIsAxisAligned(i0, i1) && + segmentIsAxisAligned(i1, i2) && + segmentIsAxisAligned(i2, i3); + if (!res) break; + } + } + return res; + } + + void tessellateQuadStrip() { + beginTex(); + int quadCount = in.vertexCount / 2 - 1; + if (fill && 1 <= quadCount) { + int nInInd = 6 * quadCount; + setRawSize(nInInd); + int idx = 0; + boolean clamp = clampQuadStrip(quadCount); + for (int qd = 1; qd < quadCount + 1; qd++) { + int i0 = 2 * (qd - 1); + int i1 = 2 * (qd - 1) + 1; + int i2 = 2 * qd + 1; + int i3 = 2 * qd; + + rawIndices[idx++] = i0; + rawIndices[idx++] = i1; + rawIndices[idx++] = i3; + + rawIndices[idx++] = i1; + rawIndices[idx++] = i2; + rawIndices[idx++] = i3; + } + splitRawIndices(clamp); + } + endTex(); + tessellateEdges(); + } + + boolean clampQuadStrip(int quadCount) { + boolean res = clamp2D(); + if (res) { + for (int qd = 1; qd < quadCount + 1; qd++) { + int i0 = 2 * (qd - 1); + int i1 = 2 * (qd - 1) + 1; + int i2 = 2 * qd + 1; + int i3 = 2 * qd; + res = segmentIsAxisAligned(i0, i1) && + segmentIsAxisAligned(i1, i2) && + segmentIsAxisAligned(i2, i3); + if (!res) break; + } + } + return res; + } + + // Uses the raw indices to split the geometry into contiguous + // index groups when the vertex indices become too large. The basic + // idea of this algorithm is to scan through the array of raw indices + // in groups of three vertices at the time (since we are always dealing + // with triangles) and create a new offset in the index cache once the + // index values go above the MAX_VERTEX_INDEX constant. The tricky part + // is that triangles in the new group might refer to vertices in a + // previous group. Since the index groups are by definition disjoint, + // these vertices need to be duplicated at the end of the corresponding + // region in the vertex array. + // + // Also to keep in mind, the ordering of the indices affects performance + // take a look at some of this references: + // http://gameangst.com/?p=9 + // http://home.comcast.net/~tom_forsyth/papers/fast_vert_cache_opt.html + // http://www.ludicon.com/castano/blog/2009/02/optimal-grid-rendering/ + void splitRawIndices(boolean clamp) { + tess.polyIndexCheck(rawSize); + int offset = tess.firstPolyIndex; + + // Current index and vertex ranges + int inInd0 = 0, inInd1 = 0; + int inMaxVert0 = 0, inMaxVert1 = 0; + + int inMaxVertRef = inMaxVert0; // Reference vertex where last break split occurred + int inMaxVertRel = -1; // Position of vertices from last range relative to + // split position. + dupCount = 0; + + IndexCache cache = tess.polyIndexCache; + // In retained mode, each shape has with its own cache item, since + // they should always be available to be rendererd individually, even + // if contained in a larger hierarchy. + int index = in.renderMode == RETAINED ? cache.addNew() : cache.getLast(); + firstPolyIndexCache = index; + + int trCount = rawSize / 3; + for (int tr = 0; tr < trCount; tr++) { + if (index == -1) index = cache.addNew(); + + int i0 = rawIndices[3 * tr + 0]; + int i1 = rawIndices[3 * tr + 1]; + int i2 = rawIndices[3 * tr + 2]; + + // Vertex indices relative to the last copy position. + int ii0 = i0 - inMaxVertRef; + int ii1 = i1 - inMaxVertRef; + int ii2 = i2 - inMaxVertRef; + + // Vertex indices relative to the current group. + int count = cache.vertexCount[index]; + int ri0, ri1, ri2; + if (ii0 < 0) { + addDupIndex(ii0); + ri0 = ii0; + } else ri0 = count + ii0; + if (ii1 < 0) { + addDupIndex(ii1); + ri1 = ii1; + } else ri1 = count + ii1; + if (ii2 < 0) { + addDupIndex(ii2); + ri2 = ii2; + } else ri2 = count + ii2; + + tess.polyIndices[offset + 3 * tr + 0] = (short) ri0; + tess.polyIndices[offset + 3 * tr + 1] = (short) ri1; + tess.polyIndices[offset + 3 * tr + 2] = (short) ri2; + + inInd1 = 3 * tr + 2; + inMaxVert1 = PApplet.max(inMaxVert1, PApplet.max(i0, i1, i2)); + inMaxVert0 = PApplet.min(inMaxVert0, PApplet.min(i0, i1, i2)); + + inMaxVertRel = PApplet.max(inMaxVertRel, PApplet.max(ri0, ri1, ri2)); + + if ((PGL.MAX_VERTEX_INDEX1 - 3 <= inMaxVertRel + dupCount && + inMaxVertRel + dupCount < PGL.MAX_VERTEX_INDEX1) || + (tr == trCount - 1)) { + // The vertex indices of the current group are about to + // surpass the MAX_VERTEX_INDEX limit, or we are at the last triangle + // so we need to wrap-up things anyways. + + int nondupCount = 0; + if (0 < dupCount) { + // Adjusting the negative indices so they correspond to vertices + // added at the end of the block. + for (int i = inInd0; i <= inInd1; i++) { + int ri = tess.polyIndices[offset + i]; + if (ri < 0) { + tess.polyIndices[offset + i] = + (short) (inMaxVertRel + 1 + dupIndexPos(ri)); + } + } + + if (inMaxVertRef <= inMaxVert1) { + // Copy non-duplicated vertices from current region first + tess.addPolyVertices(in, inMaxVertRef, inMaxVert1, clamp); + nondupCount = inMaxVert1 - inMaxVertRef + 1; + } + + // Copy duplicated vertices from previous regions last + for (int i = 0; i < dupCount; i++) { + tess.addPolyVertex(in, dupIndices[i] + inMaxVertRef, clamp); + } + } else { + // Copy non-duplicated vertices from current region first + tess.addPolyVertices(in, inMaxVert0, inMaxVert1, clamp); + nondupCount = inMaxVert1 - inMaxVert0 + 1; + } + + // Increment counts: + cache.incCounts(index, inInd1 - inInd0 + 1, nondupCount + dupCount); + lastPolyIndexCache = index; + + // Prepare all variables to start next cache: + index = -1; + inMaxVertRel = -1; + inMaxVertRef = inMaxVert1 + 1; + inMaxVert0 = inMaxVertRef; + inInd0 = inInd1 + 1; + if (dupIndices != null) Arrays.fill(dupIndices, 0, dupCount, 0); + dupCount = 0; + } + } + } + + void addDupIndex(int idx) { + if (dupIndices == null) { + dupIndices = new int[16]; + } + if (dupIndices.length == dupCount) { + int n = dupCount << 1; + + int temp[] = new int[n]; + PApplet.arrayCopy(dupIndices, 0, temp, 0, dupCount); + dupIndices = temp; + } + + if (idx < dupIndices[0]) { + // Add at the beginning + for (int i = dupCount; i > 0; i--) dupIndices[i] = dupIndices[i - 1]; + dupIndices[0] = idx; + dupCount++; + } else if (dupIndices[dupCount - 1] < idx) { + // Add at the end + dupIndices[dupCount] = idx; + dupCount++; + } else { + for (int i = 0; i < dupCount - 1; i++) { + if (dupIndices[i] == idx) break; + if (dupIndices[i] < idx && idx < dupIndices[i + 1]) { + // Insert between i and i + 1: + for (int j = dupCount; j > i + 1; j--) { + dupIndices[j] = dupIndices[j - 1]; + } + dupIndices[i + 1] = idx; + dupCount++; + break; + } + } + } + } + + int dupIndexPos(int idx) { + for (int i = 0; i < dupCount; i++) { + if (dupIndices[i] == idx) return i; + } + return 0; + } + + void setRawSize(int size) { + int size0 = rawIndices.length; + if (size0 < size) { + int size1 = expandArraySize(size0, size); + expandRawIndices(size1); + } + rawSize = size; + } + + void expandRawIndices(int n) { + int temp[] = new int[n]; + PApplet.arrayCopy(rawIndices, 0, temp, 0, rawSize); + rawIndices = temp; + } + + void beginTex() { + setFirstTexIndex(tess.polyIndexCount, tess.polyIndexCache.size - 1); + } + + void endTex() { + setLastTexIndex(tess.lastPolyIndex, tess.polyIndexCache.size - 1); + } + + void beginNoTex() { + newTexImage = null; + setFirstTexIndex(tess.polyIndexCount, tess.polyIndexCache.size - 1); + } + + void endNoTex() { + setLastTexIndex(tess.lastPolyIndex, tess.polyIndexCache.size - 1); + } + + void updateTex() { + beginTex(); + endTex(); + } + + void setFirstTexIndex(int firstIndex, int firstCache) { + if (texCache != null) { + firstTexIndex = firstIndex; + firstTexCache = PApplet.max(0, firstCache); + } + } + + void setLastTexIndex(int lastIndex, int lastCache) { + if (texCache != null) { + if (prevTexImage != newTexImage || texCache.size == 0) { + texCache.addTexture(newTexImage, firstTexIndex, firstTexCache, + lastIndex, lastCache); + } else { + texCache.setLastIndex(lastIndex, lastCache); + } + } + prevTexImage = newTexImage; + } + + // ----------------------------------------------------------------- + // + // Polygon tessellation, includes edge calculation and tessellation. + + void tessellatePolygon(boolean solid, boolean closed, boolean calcNormals) { + beginTex(); + + int nInVert = in.vertexCount; + + if (3 <= nInVert) { + firstPolyIndexCache = -1; + + initGluTess(); + boolean clamp = clampPolygon(); + callback.init(in.renderMode == RETAINED, false, calcNormals, clamp); + + if (fill) { + gluTess.beginPolygon(); + if (solid) { + // Using NONZERO winding rule for solid polygons. + gluTess.setWindingRule(PGL.TESS_WINDING_NONZERO); + } else { + // Using ODD winding rule to generate polygon with holes. + gluTess.setWindingRule(PGL.TESS_WINDING_ODD); + } + gluTess.beginContour(); + } + + if (stroke) { + beginPolygonStroke(); + beginStrokePath(); + } + + int i = 0; + int c = 0; + while (i < in.vertexCount) { + int code = VERTEX; + boolean brk = false; + if (in.codes != null && c < in.codeCount) { + code = in.codes[c++]; + if (code == BREAK && c < in.codeCount) { + brk = true; + code = in.codes[c++]; + } + } + + if (brk) { + if (stroke) { + endStrokePath(closed); + beginStrokePath(); + } + if (fill) { + gluTess.endContour(); + gluTess.beginContour(); + } + } + + if (code == BEZIER_VERTEX) { + addBezierVertex(i); + i += 3; + } else if (code == QUADRATIC_VERTEX) { + addQuadraticVertex(i); + i += 2; + } else if (code == CURVE_VERTEX) { + addCurveVertex(i); + i++; + } else { + addVertex(i); + i++; + } + } + if (stroke) { + endStrokePath(closed); + endPolygonStroke(); + } + if (fill) { + gluTess.endContour(); + gluTess.endPolygon(); + } + } + endTex(); + + if (stroke) tessellateStrokePath(); + } + + void addBezierVertex(int i) { + pg.curveVertexCount = 0; + pg.bezierInitCheck(); + pg.bezierVertexCheck(POLYGON, i); + + PMatrix3D draw = pg.bezierDrawMatrix; + + int i1 = i - 1; + float x1 = in.vertices[3*i1 + 0]; + float y1 = in.vertices[3*i1 + 1]; + float z1 = in.vertices[3*i1 + 2]; + + int strokeColor = 0; + float strokeWeight = 0; + if (stroke) { + strokeColor = in.strokeColors[i]; + strokeWeight = in.strokeWeights[i]; + } + + int fcol = 0, fa = 0, fr = 0, fg = 0, fb = 0; + int acol = 0, aa = 0, ar = 0, ag = 0, ab = 0; + int scol = 0, sa = 0, sr = 0, sg = 0, sb = 0; + int ecol = 0, ea = 0, er = 0, eg = 0, eb = 0; + float nx = 0, ny = 0, nz = 0, u = 0, v = 0, sh = 0; + if (fill) { + fcol = in.colors[i]; + fa = (fcol >> 24) & 0xFF; + fr = (fcol >> 16) & 0xFF; + fg = (fcol >> 8) & 0xFF; + fb = (fcol >> 0) & 0xFF; + + acol = in.ambient[i]; + aa = (acol >> 24) & 0xFF; + ar = (acol >> 16) & 0xFF; + ag = (acol >> 8) & 0xFF; + ab = (acol >> 0) & 0xFF; + + scol = in.specular[i]; + sa = (scol >> 24) & 0xFF; + sr = (scol >> 16) & 0xFF; + sg = (scol >> 8) & 0xFF; + sb = (scol >> 0) & 0xFF; + + ecol = in.emissive[i]; + ea = (ecol >> 24) & 0xFF; + er = (ecol >> 16) & 0xFF; + eg = (ecol >> 8) & 0xFF; + eb = (ecol >> 0) & 0xFF; + + nx = in.normals[3*i + 0]; + ny = in.normals[3*i + 1]; + nz = in.normals[3*i + 2]; + u = in.texcoords[2*i + 0]; + v = in.texcoords[2*i + 1]; + sh = in.shininess[i]; + } + + float x2 = in.vertices[3*i + 0]; + float y2 = in.vertices[3*i + 1]; + float z2 = in.vertices[3*i + 2]; + float x3 = in.vertices[3*(i+1) + 0]; + float y3 = in.vertices[3*(i+1) + 1]; + float z3 = in.vertices[3*(i+1) + 2]; + float x4 = in.vertices[3*(i+2) + 0]; + float y4 = in.vertices[3*(i+2) + 1]; + float z4 = in.vertices[3*(i+2) + 2]; + + float xplot1 = draw.m10*x1 + draw.m11*x2 + draw.m12*x3 + draw.m13*x4; + float xplot2 = draw.m20*x1 + draw.m21*x2 + draw.m22*x3 + draw.m23*x4; + float xplot3 = draw.m30*x1 + draw.m31*x2 + draw.m32*x3 + draw.m33*x4; + + float yplot1 = draw.m10*y1 + draw.m11*y2 + draw.m12*y3 + draw.m13*y4; + float yplot2 = draw.m20*y1 + draw.m21*y2 + draw.m22*y3 + draw.m23*y4; + float yplot3 = draw.m30*y1 + draw.m31*y2 + draw.m32*y3 + draw.m33*y4; + + float zplot1 = draw.m10*z1 + draw.m11*z2 + draw.m12*z3 + draw.m13*z4; + float zplot2 = draw.m20*z1 + draw.m21*z2 + draw.m22*z3 + draw.m23*z4; + float zplot3 = draw.m30*z1 + draw.m31*z2 + draw.m32*z3 + draw.m33*z4; + + for (int j = 0; j < pg.bezierDetail; j++) { + x1 += xplot1; xplot1 += xplot2; xplot2 += xplot3; + y1 += yplot1; yplot1 += yplot2; yplot2 += yplot3; + z1 += zplot1; zplot1 += zplot2; zplot2 += zplot3; + if (fill) { + double[] vertex = new double[] { + x1, y1, z1, + fa, fr, fg, fb, + nx, ny, nz, + u, v, + aa, ar, ag, ab, sa, sr, sg, sb, ea, er, eg, eb, sh}; + gluTess.addVertex(vertex); + } + if (stroke) addStrokeVertex(x1, y1, z1, strokeColor, strokeWeight); + } + } + + void addQuadraticVertex(int i) { + pg.curveVertexCount = 0; + pg.bezierInitCheck(); + pg.bezierVertexCheck(POLYGON, i); + + PMatrix3D draw = pg.bezierDrawMatrix; + + int i1 = i - 1; + float x1 = in.vertices[3*i1 + 0]; + float y1 = in.vertices[3*i1 + 1]; + float z1 = in.vertices[3*i1 + 2]; + + int strokeColor = 0; + float strokeWeight = 0; + if (stroke) { + strokeColor = in.strokeColors[i]; + strokeWeight = in.strokeWeights[i]; + } + + int fcol = 0, fa = 0, fr = 0, fg = 0, fb = 0; + int acol = 0, aa = 0, ar = 0, ag = 0, ab = 0; + int scol = 0, sa = 0, sr = 0, sg = 0, sb = 0; + int ecol = 0, ea = 0, er = 0, eg = 0, eb = 0; + float nx = 0, ny = 0, nz = 0, u = 0, v = 0, sh = 0; + if (fill) { + fcol = in.colors[i]; + fa = (fcol >> 24) & 0xFF; + fr = (fcol >> 16) & 0xFF; + fg = (fcol >> 8) & 0xFF; + fb = (fcol >> 0) & 0xFF; + + acol = in.ambient[i]; + aa = (acol >> 24) & 0xFF; + ar = (acol >> 16) & 0xFF; + ag = (acol >> 8) & 0xFF; + ab = (acol >> 0) & 0xFF; + + scol = in.specular[i]; + sa = (scol >> 24) & 0xFF; + sr = (scol >> 16) & 0xFF; + sg = (scol >> 8) & 0xFF; + sb = (scol >> 0) & 0xFF; + + ecol = in.emissive[i]; + ea = (ecol >> 24) & 0xFF; + er = (ecol >> 16) & 0xFF; + eg = (ecol >> 8) & 0xFF; + eb = (ecol >> 0) & 0xFF; + + nx = in.normals[3*i + 0]; + ny = in.normals[3*i + 1]; + nz = in.normals[3*i + 2]; + u = in.texcoords[2*i + 0]; + v = in.texcoords[2*i + 1]; + sh = in.shininess[i]; + } + + float cx = in.vertices[3*i + 0]; + float cy = in.vertices[3*i + 1]; + float cz = in.vertices[3*i + 2]; + float x = in.vertices[3*(i+1) + 0]; + float y = in.vertices[3*(i+1) + 1]; + float z = in.vertices[3*(i+1) + 2]; + + float x2 = x1 + ((cx-x1)*2/3.0f); + float y2 = y1 + ((cy-y1)*2/3.0f); + float z2 = z1 + ((cz-z1)*2/3.0f); + float x3 = x + ((cx-x)*2/3.0f); + float y3 = y + ((cy-y)*2/3.0f); + float z3 = z + ((cz-z)*2/3.0f); + float x4 = x; + float y4 = y; + float z4 = z; + + float xplot1 = draw.m10*x1 + draw.m11*x2 + draw.m12*x3 + draw.m13*x4; + float xplot2 = draw.m20*x1 + draw.m21*x2 + draw.m22*x3 + draw.m23*x4; + float xplot3 = draw.m30*x1 + draw.m31*x2 + draw.m32*x3 + draw.m33*x4; + + float yplot1 = draw.m10*y1 + draw.m11*y2 + draw.m12*y3 + draw.m13*y4; + float yplot2 = draw.m20*y1 + draw.m21*y2 + draw.m22*y3 + draw.m23*y4; + float yplot3 = draw.m30*y1 + draw.m31*y2 + draw.m32*y3 + draw.m33*y4; + + float zplot1 = draw.m10*z1 + draw.m11*z2 + draw.m12*z3 + draw.m13*z4; + float zplot2 = draw.m20*z1 + draw.m21*z2 + draw.m22*z3 + draw.m23*z4; + float zplot3 = draw.m30*z1 + draw.m31*z2 + draw.m32*z3 + draw.m33*z4; + + for (int j = 0; j < pg.bezierDetail; j++) { + x1 += xplot1; xplot1 += xplot2; xplot2 += xplot3; + y1 += yplot1; yplot1 += yplot2; yplot2 += yplot3; + z1 += zplot1; zplot1 += zplot2; zplot2 += zplot3; + if (fill) { + double[] vertex = new double[] { + x1, y1, z1, + fa, fr, fg, fb, + nx, ny, nz, + u, v, + aa, ar, ag, ab, sa, sr, sg, sb, ea, er, eg, eb, sh}; + gluTess.addVertex(vertex); + } + if (stroke) addStrokeVertex(x1, y1, z1, strokeColor, strokeWeight); + } + } + + void addCurveVertex(int i) { + pg.curveVertexCheck(POLYGON); + + float[] vertex = pg.curveVertices[pg.curveVertexCount]; + vertex[X] = in.vertices[3*i + 0]; + vertex[Y] = in.vertices[3*i + 1]; + vertex[Z] = in.vertices[3*i + 2]; + pg.curveVertexCount++; + + // draw a segment if there are enough points + if (pg.curveVertexCount > 3) { + float[] v1 = pg.curveVertices[pg.curveVertexCount - 4]; + float[] v2 = pg.curveVertices[pg.curveVertexCount - 3]; + float[] v3 = pg.curveVertices[pg.curveVertexCount - 2]; + float[] v4 = pg.curveVertices[pg.curveVertexCount - 1]; + addCurveVertexSegment(i, v1[X], v1[Y], v1[Z], + v2[X], v2[Y], v2[Z], + v3[X], v3[Y], v3[Z], + v4[X], v4[Y], v4[Z]); + } + } + + void addCurveVertexSegment(int i, float x1, float y1, float z1, + float x2, float y2, float z2, + float x3, float y3, float z3, + float x4, float y4, float z4) { + int strokeColor = 0; + float strokeWeight = 0; + if (stroke) { + strokeColor = in.strokeColors[i]; + strokeWeight = in.strokeWeights[i]; + } + + int fcol = 0, fa = 0, fr = 0, fg = 0, fb = 0; + int acol = 0, aa = 0, ar = 0, ag = 0, ab = 0; + int scol = 0, sa = 0, sr = 0, sg = 0, sb = 0; + int ecol = 0, ea = 0, er = 0, eg = 0, eb = 0; + float nx = 0, ny = 0, nz = 0, u = 0, v = 0, sh = 0; + if (fill) { + fcol = in.colors[i]; + fa = (fcol >> 24) & 0xFF; + fr = (fcol >> 16) & 0xFF; + fg = (fcol >> 8) & 0xFF; + fb = (fcol >> 0) & 0xFF; + + acol = in.ambient[i]; + aa = (acol >> 24) & 0xFF; + ar = (acol >> 16) & 0xFF; + ag = (acol >> 8) & 0xFF; + ab = (acol >> 0) & 0xFF; + + scol = in.specular[i]; + sa = (scol >> 24) & 0xFF; + sr = (scol >> 16) & 0xFF; + sg = (scol >> 8) & 0xFF; + sb = (scol >> 0) & 0xFF; + + ecol = in.emissive[i]; + ea = (ecol >> 24) & 0xFF; + er = (ecol >> 16) & 0xFF; + eg = (ecol >> 8) & 0xFF; + eb = (ecol >> 0) & 0xFF; + + nx = in.normals[3*i + 0]; + ny = in.normals[3*i + 1]; + nz = in.normals[3*i + 2]; + u = in.texcoords[2*i + 0]; + v = in.texcoords[2*i + 1]; + sh = in.shininess[i]; + } + + float x = x2; + float y = y2; + float z = z2; + + PMatrix3D draw = pg.curveDrawMatrix; + + float xplot1 = draw.m10*x1 + draw.m11*x2 + draw.m12*x3 + draw.m13*x4; + float xplot2 = draw.m20*x1 + draw.m21*x2 + draw.m22*x3 + draw.m23*x4; + float xplot3 = draw.m30*x1 + draw.m31*x2 + draw.m32*x3 + draw.m33*x4; + + float yplot1 = draw.m10*y1 + draw.m11*y2 + draw.m12*y3 + draw.m13*y4; + float yplot2 = draw.m20*y1 + draw.m21*y2 + draw.m22*y3 + draw.m23*y4; + float yplot3 = draw.m30*y1 + draw.m31*y2 + draw.m32*y3 + draw.m33*y4; + + float zplot1 = draw.m10*z1 + draw.m11*z2 + draw.m12*z3 + draw.m13*z4; + float zplot2 = draw.m20*z1 + draw.m21*z2 + draw.m22*z3 + draw.m23*z4; + float zplot3 = draw.m30*z1 + draw.m31*z2 + draw.m32*z3 + draw.m33*z4; + + if (fill) { + double[] vertex0 = new double[] { + x, y, z, + fa, fr, fg, fb, + nx, ny, nz, + u, v, + aa, ar, ag, ab, sa, sr, sg, sb, ea, er, eg, eb, sh}; + gluTess.addVertex(vertex0); + } + if (stroke) addStrokeVertex(x, y, z, strokeColor, strokeWeight); + + for (int j = 0; j < pg.curveDetail; j++) { + x += xplot1; xplot1 += xplot2; xplot2 += xplot3; + y += yplot1; yplot1 += yplot2; yplot2 += yplot3; + z += zplot1; zplot1 += zplot2; zplot2 += zplot3; + if (fill) { + double[] vertex1 = new double[] { + x, y, z, + fa, fr, fg, fb, + nx, ny, nz, + u, v, + aa, ar, ag, ab, sa, sr, sg, sb, ea, er, eg, eb, sh}; + gluTess.addVertex(vertex1); + } + if (stroke) addStrokeVertex(x, y, z, strokeColor, strokeWeight); + } + } + + void addVertex(int i) { + pg.curveVertexCount = 0; + + float x = in.vertices[3*i + 0]; + float y = in.vertices[3*i + 1]; + float z = in.vertices[3*i + 2]; + + int strokeColor = 0; + float strokeWeight = 0; + if (stroke) { + strokeColor = in.strokeColors[i]; + strokeWeight = in.strokeWeights[i]; + } + + if (fill) { + // Separating colors into individual rgba components for interpolation. + int fcol = in.colors[i]; + int fa = (fcol >> 24) & 0xFF; + int fr = (fcol >> 16) & 0xFF; + int fg = (fcol >> 8) & 0xFF; + int fb = (fcol >> 0) & 0xFF; + + int acol = in.ambient[i]; + int aa = (acol >> 24) & 0xFF; + int ar = (acol >> 16) & 0xFF; + int ag = (acol >> 8) & 0xFF; + int ab = (acol >> 0) & 0xFF; + + int scol = in.specular[i]; + int sa = (scol >> 24) & 0xFF; + int sr = (scol >> 16) & 0xFF; + int sg = (scol >> 8) & 0xFF; + int sb = (scol >> 0) & 0xFF; + + int ecol = in.emissive[i]; + int ea = (ecol >> 24) & 0xFF; + int er = (ecol >> 16) & 0xFF; + int eg = (ecol >> 8) & 0xFF; + int eb = (ecol >> 0) & 0xFF; + + float nx = in.normals[3*i + 0]; + float ny = in.normals[3*i + 1]; + float nz = in.normals[3*i + 2]; + float u = in.texcoords[2*i + 0]; + float v = in.texcoords[2*i + 1]; + float sh = in.shininess[i]; + + double[] vertex = new double[] { + x, y, z, + fa, fr, fg, fb, + nx, ny, nz, + u, v, + aa, ar, ag, ab, sa, sr, sg, sb, ea, er, eg, eb, sh}; + gluTess.addVertex(vertex); + } + if (stroke) addStrokeVertex(x, y, z, strokeColor, strokeWeight); + } + + void beginPolygonStroke() { + pathVertexCount = 0; + if (pathVertices == null) { + pathVertices = new float[3 * PGL.DEFAULT_IN_VERTICES]; + pathColors = new int[PGL.DEFAULT_IN_VERTICES]; + pathWeights = new float[PGL.DEFAULT_IN_VERTICES]; + } + } + + void endPolygonStroke() { + // Nothing to do here. + } + + void beginStrokePath() { + beginPath = pathVertexCount; + } + + void endStrokePath(boolean closed) { + int idx = pathVertexCount; + if (beginPath + 1 < idx) { + boolean begin = beginPath == idx - 2; + boolean end = begin || !closed; + in.addEdge(idx - 2, idx - 1, begin, end); + if (!end) { + in.addEdge(idx - 1, beginPath, false, false); + in.closeEdge(idx - 1, beginPath); + } + } + } + + void addStrokeVertex(float x, float y, float z, int c, float w) { + int idx = pathVertexCount; + if (beginPath + 1 < idx) { + in.addEdge(idx - 2, idx - 1, beginPath == idx - 2, false); + } + + if (pathVertexCount == pathVertices.length / 3) { + int newSize = pathVertexCount << 1; + + float vtemp[] = new float[3 * newSize]; + PApplet.arrayCopy(pathVertices, 0, vtemp, 0, 3 * pathVertexCount); + pathVertices = vtemp; + + int ctemp[] = new int[newSize]; + PApplet.arrayCopy(pathColors, 0, ctemp, 0, pathVertexCount); + pathColors = ctemp; + + float wtemp[] = new float[newSize]; + PApplet.arrayCopy(pathWeights, 0, wtemp, 0, pathVertexCount); + pathWeights = wtemp; + } + + pathVertices[3 * idx + 0] = x; + pathVertices[3 * idx + 1] = y; + pathVertices[3 * idx + 2] = z; + pathColors[idx] = c; + pathWeights[idx] = w; + + pathVertexCount++; + } + + void tessellateStrokePath() { + if (in.edgeCount == 0) return; + strokeVertices = pathVertices; + strokeColors = pathColors; + strokeWeights = pathWeights; + if (is3D) { + tessellateEdges3D(); + } else if (is2D) { + beginNoTex(); + tessellateEdges2D(); + endNoTex(); + } + } + + boolean clampPolygon() { + return false; + } + + // Tessellates the path given as parameter. This will work only in 2D. + // Based on the opengl stroke hack described here: + // http://wiki.processing.org/w/Stroke_attributes_in_OpenGL + public void tessellateLinePath(LinePath path) { + initGluTess(); + boolean clamp = clampLinePath(); + callback.init(in.renderMode == RETAINED, true, false, clamp); + + int cap = strokeCap == ROUND ? LinePath.CAP_ROUND : + strokeCap == PROJECT ? LinePath.CAP_SQUARE : + LinePath.CAP_BUTT; + int join = strokeJoin == ROUND ? LinePath.JOIN_ROUND : + strokeJoin == BEVEL ? LinePath.JOIN_BEVEL : + LinePath.JOIN_MITER; + + // Make the outline of the stroke from the path + LinePath strokedPath = LinePath.createStrokedPath(path, strokeWeight, + cap, join); + + gluTess.beginPolygon(); + + double[] vertex; + float[] coords = new float[6]; + + LinePath.PathIterator iter = strokedPath.getPathIterator(); + int rule = iter.getWindingRule(); + switch(rule) { + case LinePath.WIND_EVEN_ODD: + gluTess.setWindingRule(PGL.TESS_WINDING_ODD); + break; + case LinePath.WIND_NON_ZERO: + gluTess.setWindingRule(PGL.TESS_WINDING_NONZERO); + break; + } + + while (!iter.isDone()) { + switch (iter.currentSegment(coords)) { + + case LinePath.SEG_MOVETO: + gluTess.beginContour(); + + // $FALL-THROUGH$ + case LinePath.SEG_LINETO: + // Vertex data includes coordinates, colors, normals, texture + // coordinates, and material properties. + vertex = new double[] { coords[0], coords[1], 0, + coords[2], coords[3], coords[4], coords[5], + 0, 0, 1, + 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + + gluTess.addVertex(vertex); + + break; + case LinePath.SEG_CLOSE: + gluTess.endContour(); + break; + } + iter.next(); + } + gluTess.endPolygon(); + } + + boolean clampLinePath() { + return clamp2D() && + strokeCap == PROJECT && strokeJoin == BEVEL && + !subPixelStroke(strokeWeight); + } + + ///////////////////////////////////////// + + // Interesting notes about using the GLU tessellator to render thick + // polylines: + // http://stackoverflow.com/questions/687173/how-do-i-render-thick-2d-lines-as-polygons + // + // "...Since I disliked the tesselator API I lifted the tesselation code + // from the free SGI OpenGL reference implementation, rewrote the entire + // front-end and added memory pools to get the number of allocations down. + // It took two days to do this, but it was well worth it (like factor five + // performance improvement)..." + // + // This C implementation of GLU could be useful: + // http://code.google.com/p/glues/ + // to eventually come up with an optimized GLU tessellator in native code. + protected class TessellatorCallback implements PGL.TessellatorCallback { + boolean calcNormals; + boolean strokeTess; + boolean clampXY; + IndexCache cache; + int cacheIndex; + int vertFirst; + int vertCount; + int vertOffset; + int primitive; + + public void init(boolean addCache, boolean strokeTess, boolean calcNorm, + boolean clampXY) { + this.strokeTess = strokeTess; + this.calcNormals = calcNorm; + this.clampXY = clampXY; + + cache = tess.polyIndexCache; + if (addCache) { + cache.addNew(); + } + } + + public void begin(int type) { + cacheIndex = cache.getLast(); + if (firstPolyIndexCache == -1) { + firstPolyIndexCache = cacheIndex; + } + if (strokeTess && firstLineIndexCache == -1) { + firstLineIndexCache = cacheIndex; + } + + vertFirst = cache.vertexCount[cacheIndex]; + vertOffset = cache.vertexOffset[cacheIndex]; + vertCount = 0; + + if (type == PGL.TRIANGLE_FAN) primitive = TRIANGLE_FAN; + else if (type == PGL.TRIANGLE_STRIP) primitive = TRIANGLE_STRIP; + else if (type == PGL.TRIANGLES) primitive = TRIANGLES; + } + + public void end() { + if (PGL.MAX_VERTEX_INDEX1 <= vertFirst + vertCount) { + // We need a new index block for the new batch of + // vertices resulting from this primitive. tessVert can + // be safely assumed here to be less or equal than + // MAX_VERTEX_INDEX1 because the condition was checked + // every time a new vertex was emitted (see vertex() below). + //tessBlock = tess.addFillIndexBlock(tessBlock); + cacheIndex = cache.addNew(); + vertFirst = cache.vertexCount[cacheIndex]; + vertOffset = cache.vertexOffset[cacheIndex]; + } + + int indCount = 0; + switch (primitive) { + case TRIANGLE_FAN: + indCount = 3 * (vertCount - 2); + for (int i = 1; i < vertCount - 1; i++) { + addIndex(0); + addIndex(i); + addIndex(i + 1); + if (calcNormals) calcTriNormal(0, i, i + 1); + } + break; + case TRIANGLE_STRIP: + indCount = 3 * (vertCount - 2); + for (int i = 1; i < vertCount - 1; i++) { + if (i % 2 == 0) { + addIndex(i + 1); + addIndex(i); + addIndex(i - 1); + if (calcNormals) calcTriNormal(i + 1, i, i - 1); + } else { + addIndex(i - 1); + addIndex(i); + addIndex(i + 1); + if (calcNormals) calcTriNormal(i - 1, i, i + 1); + } + } + break; + case TRIANGLES: + indCount = vertCount; + for (int i = 0; i < vertCount; i++) { + addIndex(i); + } + if (calcNormals) { + for (int tr = 0; tr < vertCount / 3; tr++) { + int i0 = 3 * tr + 0; + int i1 = 3 * tr + 1; + int i2 = 3 * tr + 2; + calcTriNormal(i0, i1, i2); + } + } + break; + } + + cache.incCounts(cacheIndex, indCount, vertCount); + lastPolyIndexCache = cacheIndex; + if (strokeTess) { + lastLineIndexCache = cacheIndex; + } + } + + protected void addIndex(int tessIdx) { + tess.polyIndexCheck(); + tess.polyIndices[tess.polyIndexCount - 1] = + (short) (vertFirst + tessIdx); + } + + protected void calcTriNormal(int tessIdx0, int tessIdx1, int tessIdx2) { + tess.calcPolyNormal(vertFirst + vertOffset + tessIdx0, + vertFirst + vertOffset + tessIdx1, + vertFirst + vertOffset + tessIdx2); + } + + public void vertex(Object data) { + if (data instanceof double[]) { + double[] d = (double[]) data; + int l = d.length; + if (l < 25) { + throw new RuntimeException("TessCallback vertex() data is not " + + "of length 25"); + } + + if (vertCount < PGL.MAX_VERTEX_INDEX1) { + // Combining individual rgba components back into int color values + int fcolor = + ((int)d[ 3]<<24) | ((int)d[ 4]<<16) | ((int)d[ 5]<<8) | (int)d[ 6]; + int acolor = + ((int)d[12]<<24) | ((int)d[13]<<16) | ((int)d[14]<<8) | (int)d[15]; + int scolor = + ((int)d[16]<<24) | ((int)d[17]<<16) | ((int)d[18]<<8) | (int)d[19]; + int ecolor = + ((int)d[20]<<24) | ((int)d[21]<<16) | ((int)d[22]<<8) | (int)d[23]; + + tess.addPolyVertex((float) d[ 0], (float) d[ 1], (float) d[ 2], + fcolor, + (float) d[ 7], (float) d[ 8], (float) d[ 9], + (float) d[10], (float) d[11], + acolor, scolor, ecolor, + (float) d[24], clampXY); + + vertCount++; + } else { + throw new RuntimeException("The tessellator is generating too " + + "many vertices, reduce complexity of " + + "shape."); + } + + } else { + throw new RuntimeException("TessCallback vertex() data not " + + "understood"); + } + } + + public void error(int errnum) { + String estring = pg.pgl.tessError(errnum); + PGraphics.showWarning(TESSELLATION_ERROR, estring); + } + + /** + * Implementation of the GLU_TESS_COMBINE callback. + * @param coords is the 3-vector of the new vertex + * @param data is the vertex data to be combined, up to four elements. + * This is useful when mixing colors together or any other + * user data that was passed in to gluTessVertex. + * @param weight is an array of weights, one for each element of "data" + * that should be linearly combined for new values. + * @param outData is the set of new values of "data" after being + * put back together based on the weights. it's passed back as a + * single element Object[] array because that's the closest + * that Java gets to a pointer. + */ + public void combine(double[] coords, Object[] data, + float[] weight, Object[] outData) { + double[] vertex = new double[25 + 8]; + vertex[0] = coords[0]; + vertex[1] = coords[1]; + vertex[2] = coords[2]; + + // Calculating the rest of the vertex parameters (color, + // normal, texcoords) as the linear combination of the + // combined vertices. + for (int i = 3; i < 25; i++) { + vertex[i] = 0; + for (int j = 0; j < 4; j++) { + double[] vertData = (double[])data[j]; + if (vertData != null) { + vertex[i] += weight[j] * vertData[i]; + } + } + } + + // Normalizing normal vector, since the weighted + // combination of normal vectors is not necessarily + // normal. + double sum = vertex[7] * vertex[7] + + vertex[8] * vertex[8] + + vertex[9] * vertex[9]; + double len = Math.sqrt(sum); + vertex[7] /= len; + vertex[8] /= len; + vertex[9] /= len; + + outData[0] = vertex; + } + } + } +} diff --git a/java/libraries/jogl/src/processing/jogl/PJOGL.java b/java/libraries/jogl/src/processing/jogl/PJOGL.java new file mode 100644 index 000000000..84166d6fb --- /dev/null +++ b/java/libraries/jogl/src/processing/jogl/PJOGL.java @@ -0,0 +1,2624 @@ +package processing.opengl; + +import java.awt.Canvas; +import java.awt.Font; +import java.awt.FontMetrics; +import java.awt.Shape; +import java.awt.font.FontRenderContext; +import java.awt.font.GlyphVector; +import java.awt.geom.PathIterator; +import java.io.IOException; +import java.net.URL; +import java.nio.Buffer; +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +//import java.util.concurrent.CountDownLatch; + +import javax.media.opengl.GL; +import javax.media.opengl.GL2; +import javax.media.opengl.GL2ES1; +import javax.media.opengl.GL2ES2; +import javax.media.opengl.GL2ES3; +import javax.media.opengl.GL2GL3; +import javax.media.opengl.GLAutoDrawable; +import javax.media.opengl.GLCapabilitiesImmutable; +import javax.media.opengl.GLContext; +import javax.media.opengl.GLDrawable; +//import javax.media.opengl.GLEventListener; +//import javax.media.opengl.GLFBODrawable; +import javax.media.opengl.GLProfile; +//import javax.media.opengl.awt.GLCanvas; +import javax.media.opengl.fixedfunc.GLMatrixFunc; +import javax.media.opengl.glu.GLU; +import javax.media.opengl.glu.GLUtessellator; +import javax.media.opengl.glu.GLUtessellatorCallbackAdapter; + +import processing.core.PApplet; +import processing.core.PGraphics; + +//import com.jogamp.newt.awt.NewtCanvasAWT; +//import com.jogamp.newt.opengl.GLWindow; +//import com.jogamp.opengl.FBObject; + + +public class PJOGL extends PGL { + // OpenGL profile to use (2, 3 or 4) + public static int PROFILE = 2; + + // Enables/disables Retina support on OSX + public static boolean RETINA = false; + + // The two windowing toolkits available to use in JOGL: + public static final int AWT = 0; // http://jogamp.org/wiki/index.php/Using_JOGL_in_AWT_SWT_and_Swing + public static final int NEWT = 1; // http://jogamp.org/jogl/doc/NEWT-Overview.html + + // ........................................................ + + // Public members to access the underlying GL objects and context + + /** Basic GL functionality, common to all profiles */ + public GL gl; + + /** GLU interface **/ + public GLU glu; + + /** The rendering context (holds rendering state info) */ + public GLContext context; + + /** The canvas where OpenGL rendering takes place */ + public Canvas canvas; + + /** Selected GL profile */ + public static GLProfile profile; + + // ........................................................ + + // Additional parameters + + /** Time that the Processing's animation thread will wait for JOGL's rendering + * thread to be done with a single frame. + */ + protected static int DRAW_TIMEOUT_MILLIS = 500; + + // ........................................................ + + // OS-specific configuration + + /* + protected static int WINDOW_TOOLKIT; + protected static int EVENTS_TOOLKIT; + protected static boolean USE_JOGL_FBOLAYER; + static { + if (PApplet.platform == PConstants.WINDOWS) { + // Using AWT on Windows because NEWT displays a black background while + // initializing, and the cursor functions don't work. GLWindow has some + // functions for basic cursor handling (hide/show): + // GLWindow.setPointerVisible(false); + // but apparently nothing to set the cursor icon: + // https://jogamp.org/bugzilla/show_bug.cgi?id=409 + WINDOW_TOOLKIT = AWT; + EVENTS_TOOLKIT = AWT; + USE_FBOLAYER_BY_DEFAULT = false; + USE_JOGL_FBOLAYER = false; + } else if (PApplet.platform == PConstants.MACOSX) { + // Note: The JOGL FBO layer (in 2.0.2) seems incompatible with NEWT. + WINDOW_TOOLKIT = AWT; + EVENTS_TOOLKIT = AWT; + USE_FBOLAYER_BY_DEFAULT = true; + USE_JOGL_FBOLAYER = true; + } else if (PApplet.platform == PConstants.LINUX) { + WINDOW_TOOLKIT = AWT; + EVENTS_TOOLKIT = AWT; + USE_FBOLAYER_BY_DEFAULT = false; + USE_JOGL_FBOLAYER = false; + } else if (PApplet.platform == PConstants.OTHER) { + WINDOW_TOOLKIT = NEWT; // NEWT works on the Raspberry pi? + EVENTS_TOOLKIT = NEWT; + USE_FBOLAYER_BY_DEFAULT = false; + USE_JOGL_FBOLAYER = false; + } + } +*/ + +// protected static boolean USE_FBOLAYER_BY_DEFAULT = false; +// protected static boolean USE_JOGL_FBOLAYER = false; + + // ........................................................ + + // Protected JOGL-specific objects needed to access the GL profiles + + /** The capabilities of the OpenGL rendering surface */ + protected GLCapabilitiesImmutable capabilities; + + /** The rendering surface */ + protected GLDrawable drawable; + + /** GLES2 functionality (shaders, etc) */ + protected GL2ES2 gl2; + + /** GL3 interface */ + protected GL2GL3 gl3; + + /** GL2 desktop functionality (blit framebuffer, map buffer range, + * multisampled renderbuffers) */ + protected GL2 gl2x; + + /** The AWT-OpenGL canvas */ +// protected GLCanvas canvasAWT; + + /** The NEWT window */ +// protected GLWindow windowNEWT; + + /** The NEWT-OpenGL canvas */ +// protected NewtCanvasAWT canvasNEWT; + + /** The listener that fires the frame rendering in Processing */ +// protected PGLListener listener; + + /** This countdown latch is used to maintain the synchronization between + * Processing's drawing thread and JOGL's rendering thread */ +// protected CountDownLatch drawLatch = new CountDownLatch(0); + + /** Flag used to do request final display() call to make sure that the + * buffers are properly swapped. + */ +// protected boolean prevCanDraw = false; + + /** Stores exceptions that ocurred during drawing */ + protected Exception drawException; + + // ........................................................ + + // JOGL's FBO-layer + + /** Back (== draw, current frame) buffer */ +// protected FBObject backFBO; + /** Sink buffer, used in the multisampled case */ +// protected FBObject sinkFBO; + /** Front (== read, previous frame) buffer */ +// protected FBObject frontFBO; +// protected FBObject.TextureAttachment backTexAttach; +// protected FBObject.TextureAttachment frontTexAttach; + +// protected boolean changedFrontTex = false; +// protected boolean changedBackTex = false; + + // ........................................................ + + // Retina support + + int pixel_scale = 1; + + // ........................................................ + + // Utility arrays to copy projection/modelview matrices to GL + + protected float[] projMatrix; + protected float[] mvMatrix; + + // ........................................................ + + // Static initialization for some parameters that need to be different for + // JOGL + + static { + MIN_DIRECT_BUFFER_SIZE = 2; + INDEX_TYPE = GL.GL_UNSIGNED_SHORT; + } + + + /////////////////////////////////////////////////////////////// + + // Initialization, finalization + + + public PJOGL(PGraphicsOpenGL pg) { + super(pg); + glu = new GLU(); + } + + + /* + @Override + public Canvas getCanvas() { + return canvas; + } +*/ + + + protected void setFps(float fps) { + if (!setFps || targetFps != fps) { + if (60 < fps) { + // Disables v-sync + gl.setSwapInterval(0); + } else if (30 < fps) { + gl.setSwapInterval(1); + } else { + gl.setSwapInterval(2); + } + targetFps = currentFps = fps; + setFps = true; + } + } + + + /* + @Override + protected void initSurface(int antialias) { + + if (profile == null) { + if (PROFILE == 2) { + try { + profile = GLProfile.getGL2ES1(); + } catch (GLException ex) { + profile = GLProfile.getMaxFixedFunc(true); + } + } else if (PROFILE == 3) { + try { + profile = GLProfile.getGL2GL3(); + } catch (GLException ex) { + profile = GLProfile.getMaxProgrammable(true); + } + if (!profile.isGL3()) { + PGraphics.showWarning("Requested profile GL3 but is not available, got: " + profile); + } + } else if (PROFILE == 4) { + try { + profile = GLProfile.getGL4ES3(); + } catch (GLException ex) { + profile = GLProfile.getMaxProgrammable(true); + } + if (!profile.isGL4()) { + PGraphics.showWarning("Requested profile GL4 but is not available, got: " + profile); + } + } else throw new RuntimeException(UNSUPPORTED_GLPROF_ERROR); + + if (2 < PROFILE) { + texVertShaderSource = convertVertexSource(texVertShaderSource, 120, 150); + tex2DFragShaderSource = convertFragmentSource(tex2DFragShaderSource, 120, 150); + texRectFragShaderSource = convertFragmentSource(texRectFragShaderSource, 120, 150); + } + } + + if (canvasAWT != null || canvasNEWT != null) { + // Restarting... + if (canvasAWT != null) { + canvasAWT.removeGLEventListener(listener); + pg.parent.removeListeners(canvasAWT); + pg.parent.remove(canvasAWT); + } else if (canvasNEWT != null) { + windowNEWT.removeGLEventListener(listener); + pg.parent.remove(canvasNEWT); + } + sinkFBO = backFBO = frontFBO = null; + } + + // Setting up the desired capabilities; + GLCapabilities caps = new GLCapabilities(profile); + caps.setAlphaBits(REQUESTED_ALPHA_BITS); + caps.setDepthBits(REQUESTED_DEPTH_BITS); + caps.setStencilBits(REQUESTED_STENCIL_BITS); + + caps.setBackgroundOpaque(true); + caps.setOnscreen(true); + if (USE_FBOLAYER_BY_DEFAULT) { + if (USE_JOGL_FBOLAYER) { + caps.setPBuffer(false); + caps.setFBO(true); + if (1 < antialias) { + caps.setSampleBuffers(true); + caps.setNumSamples(antialias); + } else { + caps.setSampleBuffers(false); + } + fboLayerRequested = false; + } else { + caps.setPBuffer(false); + caps.setFBO(false); + caps.setSampleBuffers(false); + fboLayerRequested = 1 < antialias; + } + } else { + if (1 < antialias) { + caps.setSampleBuffers(true); + caps.setNumSamples(antialias); + } else { + caps.setSampleBuffers(false); + } + fboLayerRequested = false; + } + caps.setDepthBits(REQUESTED_DEPTH_BITS); + caps.setStencilBits(REQUESTED_STENCIL_BITS); + caps.setAlphaBits(REQUESTED_ALPHA_BITS); + reqNumSamples = qualityToSamples(antialias); + + if (WINDOW_TOOLKIT == AWT) { + canvasAWT = new GLCanvas(caps); + + if (RETINA) { + canvasAWT.setSurfaceScale(new int[] { ScalableSurface.AUTOMAX_PIXELSCALE, + ScalableSurface.AUTOMAX_PIXELSCALE }); + retf = 2; + } else { + canvasAWT.setSurfaceScale(new int[] { ScalableSurface.IDENTITY_PIXELSCALE, + ScalableSurface.IDENTITY_PIXELSCALE }); + } + + canvasAWT.setBounds(0, 0, pg.width, pg.height); + canvasAWT.setBackground(new Color(pg.backgroundColor, true)); + canvasAWT.setFocusable(true); + + pg.parent.setLayout(new BorderLayout()); + pg.parent.add(canvasAWT, BorderLayout.CENTER); + canvasAWT.requestFocusInWindow(); + + + + canvas = canvasAWT; + canvasNEWT = null; + } else if (WINDOW_TOOLKIT == NEWT) { + windowNEWT = GLWindow.create(caps); + canvasNEWT = new NewtCanvasAWT(windowNEWT); + canvasNEWT.setBounds(0, 0, pg.width, pg.height); + canvasNEWT.setBackground(new Color(pg.backgroundColor, true)); + canvasNEWT.setFocusable(true); + + pg.parent.setLayout(new BorderLayout()); + pg.parent.add(canvasNEWT, BorderLayout.CENTER); + canvasNEWT.requestFocusInWindow(); + + int[] reqSurfacePixelScale = new int[] { ScalableSurface.AUTOMAX_PIXELSCALE, ScalableSurface.AUTOMAX_PIXELSCALE }; + windowNEWT.setSurfaceScale(reqSurfacePixelScale); + + canvas = canvasNEWT; + canvasAWT = null; + } + + pg.parent.defaultSize = false; + registerListeners(); + + + fboLayerCreated = false; + fboLayerInUse = false; + firstFrame = true; + setFps = false; + } + */ + +/* + @Override + protected void reinitSurface() { + sinkFBO = backFBO = frontFBO = null; + fboLayerCreated = false; + fboLayerInUse = false; + firstFrame = true; + pg.parent.defaultSize = false; + } +*/ + +// @Override +// protected void registerListeners() { +// if (WINDOW_TOOLKIT == AWT) { +// pg.parent.addListeners(canvasAWT); +// +// listener = new PGLListener(); +// canvasAWT.addGLEventListener(listener); +// } else if (WINDOW_TOOLKIT == NEWT) { +// if (EVENTS_TOOLKIT == NEWT) { +// NEWTMouseListener mouseListener = new NEWTMouseListener(); +// windowNEWT.addMouseListener(mouseListener); +// NEWTKeyListener keyListener = new NEWTKeyListener(); +// windowNEWT.addKeyListener(keyListener); +// NEWTWindowListener winListener = new NEWTWindowListener(); +// windowNEWT.addWindowListener(winListener); +// } else if (EVENTS_TOOLKIT == AWT) { +// pg.parent.addListeners(canvasNEWT); +// } +// +// listener = new PGLListener(); +// windowNEWT.addGLEventListener(listener); +// } +// +// if (canvas != null) { +// canvas.setFocusTraversalKeysEnabled(false); +// } +// } + + +// @Override +// protected void deleteSurface() { +// super.deleteSurface(); +// +// if (canvasAWT != null) { +// canvasAWT.removeGLEventListener(listener); +// pg.parent.removeListeners(canvasAWT); +// } else if (canvasNEWT != null) { +// windowNEWT.removeGLEventListener(listener); +// } +// } + +/* + @Override + protected int getReadFramebuffer() { + if (fboLayerInUse) { + return glColorFbo.get(0); + } else if (capabilities.isFBO()) { + return context.getDefaultReadFramebuffer(); + } else { + return 0; + } + } + + + @Override + protected int getDrawFramebuffer() { + if (fboLayerInUse) { + if (1 < numSamples) { + return glMultiFbo.get(0); + } else { + return glColorFbo.get(0); + } + } else if (capabilities.isFBO()) { + return context.getDefaultDrawFramebuffer(); + } else { + return 0; + } + } + + + @Override + protected int getDefaultDrawBuffer() { + if (fboLayerInUse) { + return COLOR_ATTACHMENT0; + } else if (capabilities.isFBO()) { + return GL.GL_COLOR_ATTACHMENT0; + } else if (capabilities.getDoubleBuffered()) { + return GL.GL_BACK; + } else { + return GL.GL_FRONT; + } + } + + + @Override + protected int getDefaultReadBuffer() { + if (fboLayerInUse) { + return COLOR_ATTACHMENT0; + } else if (capabilities.isFBO()) { + return GL.GL_COLOR_ATTACHMENT0; + } else if (capabilities.getDoubleBuffered()) { + return GL.GL_BACK; + } else { + return GL.GL_FRONT; + } + } + + + @Override + protected boolean isFBOBacked() { + return super.isFBOBacked() || capabilities.isFBO(); + } + + + @Override + protected int getDepthBits() { + return capabilities.getDepthBits(); + } + + + @Override + protected int getStencilBits() { + return capabilities.getStencilBits(); + } + + + @Override + protected Texture wrapBackTexture(Texture texture) { + if (texture == null || changedBackTex) { + if (USE_JOGL_FBOLAYER) { + texture = new Texture(pg); + texture.init(pg.width, pg.height, + backTexAttach.getName(), TEXTURE_2D, RGBA, + backTexAttach.getWidth(), backTexAttach.getHeight(), + backTexAttach.minFilter, backTexAttach.magFilter, + backTexAttach.wrapS, backTexAttach.wrapT); + texture.invertedY(true); + texture.colorBuffer(true); + pg.setCache(pg, texture); + } else { + texture = super.wrapBackTexture(null); + } + } else { + if (USE_JOGL_FBOLAYER) { + texture.glName = backTexAttach.getName(); + } else { + texture = super.wrapBackTexture(texture); + } + } + return texture; + } + + + @Override + protected Texture wrapFrontTexture(Texture texture) { + if (texture == null || changedFrontTex) { + if (USE_JOGL_FBOLAYER) { + texture = new Texture(pg); + texture.init(pg.width, pg.height, + backTexAttach.getName(), TEXTURE_2D, RGBA, + frontTexAttach.getWidth(), frontTexAttach.getHeight(), + frontTexAttach.minFilter, frontTexAttach.magFilter, + frontTexAttach.wrapS, frontTexAttach.wrapT); + texture.invertedY(true); + texture.colorBuffer(true); + } else { + texture = super.wrapFrontTexture(null); + } + } else { + if (USE_JOGL_FBOLAYER) { + texture.glName = frontTexAttach.getName(); + } else { + texture = super.wrapFrontTexture(texture); + } + } + return texture; + } + + + @Override + protected void bindFrontTexture() { + if (USE_JOGL_FBOLAYER) { + usingFrontTex = true; + if (!texturingIsEnabled(TEXTURE_2D)) { + enableTexturing(TEXTURE_2D); + } + bindTexture(TEXTURE_2D, frontTexAttach.getName()); + } else super.bindFrontTexture(); + } + + + @Override + protected void unbindFrontTexture() { + if (USE_JOGL_FBOLAYER) { + if (textureIsBound(TEXTURE_2D, frontTexAttach.getName())) { + // We don't want to unbind another texture + // that might be bound instead of this one. + if (!texturingIsEnabled(TEXTURE_2D)) { + enableTexturing(TEXTURE_2D); + bindTexture(TEXTURE_2D, 0); + disableTexturing(TEXTURE_2D); + } else { + bindTexture(TEXTURE_2D, 0); + } + } + } else super.unbindFrontTexture(); + } + + + @Override + protected void syncBackTexture() { + if (USE_JOGL_FBOLAYER) { + if (usingFrontTex) needSepFrontTex = true; + if (1 < numSamples && backFBO != null) { + backFBO.syncSamplingSink(gl); + backFBO.bind(gl); + } + } else super.syncBackTexture(); + } + + + @Override + protected void beginDraw(boolean clear0) { + if (!setFps) setFps(targetFps); + if (USE_JOGL_FBOLAYER) return; + super.beginDraw(clear0); + } + + + @Override + protected void endDraw(boolean clear0) { + if (isFBOBacked()) { + if (USE_JOGL_FBOLAYER) { + if (!clear0 && isFBOBacked() && !isMultisampled() && + frontFBO != null && backFBO != null) { + // Draw the back texture into the front texture, which will be used as + // back texture in the next frame. Otherwise flickering will occur if + // the sketch uses "incremental drawing" (background() not called). + frontFBO.bind(gl); + gl.glDisable(GL.GL_BLEND); + drawTexture(TEXTURE_2D, backTexAttach.getName(), + backTexAttach.getWidth(), backTexAttach.getHeight(), + pg.width, pg.height, + 0, 0, pg.width, pg.height, 0, 0, pg.width, pg.height); + backFBO.bind(gl); + } + } else { + super.endDraw(clear0); + } + } + } +*/ + + @Override + protected void getGL(PGL pgl) { + PJOGL pjogl = (PJOGL)pgl; + + this.drawable = pjogl.drawable; + this.context = pjogl.context; + this.glContext = pjogl.glContext; + setThread(pjogl.glThread); + + this.gl = pjogl.gl; + this.gl2 = pjogl.gl2; + this.gl2x = pjogl.gl2x; + this.gl3 = pjogl.gl3; + } + + + protected void getGL(GLAutoDrawable glDrawable) { + context = glDrawable.getContext(); + glContext = context.hashCode(); + setThread(Thread.currentThread()); + + gl = context.getGL(); + gl2 = gl.getGL2ES2(); + try { + gl2x = gl.getGL2(); + } catch (javax.media.opengl.GLException e) { + gl2x = null; + } + try { + gl3 = gl.getGL2GL3(); + } catch (javax.media.opengl.GLException e) { + gl3 = null; + } + } + + + + /* + @Override + protected boolean canDraw() { + return true; +// return pg.initialized; +// && pg.parent.isDisplayable(); + } + + + @Override + protected void requestFocus() { } + + + @Override + protected void requestDraw() { + + drawException = null; + boolean canDraw = pg.parent.canDraw(); + if (pg.initialized && (canDraw || prevCanDraw)) { + drawLatch = new CountDownLatch(1); + if (WINDOW_TOOLKIT == AWT) { + canvasAWT.display(); + } else if (WINDOW_TOOLKIT == NEWT) { + windowNEWT.display(); + } + try { + drawLatch.await(DRAW_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + if (canDraw) prevCanDraw = true; + else prevCanDraw = false; + } + + // Throw wherever exception happened during drawing outside the GL thread + // to it is properly picked up by the PDE. + if (drawException != null) { + if (drawException instanceof RuntimeException) { + throw (RuntimeException)drawException; + } else { + throw new RuntimeException(drawException); + } + } + } + + + @Override + protected void swapBuffers() { + if (WINDOW_TOOLKIT == AWT) { + canvasAWT.swapBuffers(); + } else if (WINDOW_TOOLKIT == NEWT) { + windowNEWT.swapBuffers(); + } + } + */ + + + @Override + protected void beginGL() { + if (gl2x != null) { + if (projMatrix == null) { + projMatrix = new float[16]; + } + gl2x.glMatrixMode(GLMatrixFunc.GL_PROJECTION); + projMatrix[ 0] = pg.projection.m00; + projMatrix[ 1] = pg.projection.m10; + projMatrix[ 2] = pg.projection.m20; + projMatrix[ 3] = pg.projection.m30; + projMatrix[ 4] = pg.projection.m01; + projMatrix[ 5] = pg.projection.m11; + projMatrix[ 6] = pg.projection.m21; + projMatrix[ 7] = pg.projection.m31; + projMatrix[ 8] = pg.projection.m02; + projMatrix[ 9] = pg.projection.m12; + projMatrix[10] = pg.projection.m22; + projMatrix[11] = pg.projection.m32; + projMatrix[12] = pg.projection.m03; + projMatrix[13] = pg.projection.m13; + projMatrix[14] = pg.projection.m23; + projMatrix[15] = pg.projection.m33; + gl2x.glLoadMatrixf(projMatrix, 0); + + if (mvMatrix == null) { + mvMatrix = new float[16]; + } + gl2x.glMatrixMode(GLMatrixFunc.GL_MODELVIEW); + mvMatrix[ 0] = pg.modelview.m00; + mvMatrix[ 1] = pg.modelview.m10; + mvMatrix[ 2] = pg.modelview.m20; + mvMatrix[ 3] = pg.modelview.m30; + mvMatrix[ 4] = pg.modelview.m01; + mvMatrix[ 5] = pg.modelview.m11; + mvMatrix[ 6] = pg.modelview.m21; + mvMatrix[ 7] = pg.modelview.m31; + mvMatrix[ 8] = pg.modelview.m02; + mvMatrix[ 9] = pg.modelview.m12; + mvMatrix[10] = pg.modelview.m22; + mvMatrix[11] = pg.modelview.m32; + mvMatrix[12] = pg.modelview.m03; + mvMatrix[13] = pg.modelview.m13; + mvMatrix[14] = pg.modelview.m23; + mvMatrix[15] = pg.modelview.m33; + gl2x.glLoadMatrixf(mvMatrix, 0); + } + } + + + @Override + protected boolean hasFBOs() { + if (context.hasBasicFBOSupport()) return true; + else return super.hasFBOs(); + } + + + @Override + protected boolean hasShaders() { + if (context.hasGLSL()) return true; + else return super.hasShaders(); + } + + + /////////////////////////////////////////////////////////// + + // JOGL event listeners + +/* + protected void getBuffers(GLWindow glWindow) { + if (false) { +// if (capabilities.isFBO()) { +// if (USE_JOGL_FBOLAYER && capabilities.isFBO()) { + // The onscreen drawing surface is backed by an FBO layer. + GLFBODrawable fboDrawable = null; + fboDrawable = (GLFBODrawable)glWindow.getDelegatedDrawable(); + + if (fboDrawable != null) { + backFBO = fboDrawable.getFBObject(GL.GL_BACK); + if (1 < numSamples) { + if (needSepFrontTex) { + // When using multisampled FBO, the back buffer is the MSAA + // surface so it cannot be read from. The sink buffer contains + // the readable 2D texture. + // In this case, we create an auxiliary "front" buffer that it is + // swapped with the sink buffer at the beginning of each frame. + // In this way, we always have a readable copy of the previous + // frame in the front texture, while the back is synchronized + // with the contents of the MSAA back buffer when requested. + if (frontFBO == null) { + // init + frontFBO = new FBObject(); + frontFBO.reset(gl, pg.width, pg.height, numSamples); + frontFBO.attachTexture2D(gl, 0, true); + sinkFBO = backFBO.getSamplingSinkFBO(); + changedFrontTex = changedBackTex = true; + } else { + // swap + FBObject temp = sinkFBO; + sinkFBO = frontFBO; + frontFBO = temp; + backFBO.setSamplingSink(sinkFBO); + changedFrontTex = changedBackTex = false; + } + backTexAttach = (FBObject.TextureAttachment) sinkFBO. + getColorbuffer(0); + frontTexAttach = (FBObject.TextureAttachment)frontFBO. + getColorbuffer(0); + } else { + changedFrontTex = changedBackTex = sinkFBO == null; + + // Default setting (to save resources): the front and back + // textures are the same. + sinkFBO = backFBO.getSamplingSinkFBO(); + backTexAttach = (FBObject.TextureAttachment) sinkFBO. + getColorbuffer(0); + frontTexAttach = backTexAttach; + } + } else { + // w/out multisampling, rendering is done on the back buffer. + frontFBO = fboDrawable.getFBObject(GL.GL_FRONT); + backTexAttach = (FBObject.TextureAttachment) backFBO.getColorbuffer(0); + frontTexAttach = (FBObject.TextureAttachment) frontFBO.getColorbuffer(0); + } + } + + } + } +*/ + + protected void init(GLAutoDrawable glDrawable) { + firstFrame = true; + capabilities = glDrawable.getChosenGLCapabilities(); + if (!hasFBOs()) { + throw new RuntimeException(MISSING_FBO_ERROR); + } + if (!hasShaders()) { + throw new RuntimeException(MISSING_GLSL_ERROR); + } +// if (USE_JOGL_FBOLAYER && capabilities.isFBO()) { +// int maxs = maxSamples(); +// numSamples = PApplet.min(capabilities.getNumSamples(), maxs); +// } + } + + /* + protected class PGLListener implements GLEventListener { + public PGLListener() {} + + @Override + public void display(GLAutoDrawable glDrawable) { + + getGL(glDrawable); + + if (USE_JOGL_FBOLAYER && capabilities.isFBO()) { + // The onscreen drawing surface is backed by an FBO layer. + GLFBODrawable fboDrawable = null; + + if (WINDOW_TOOLKIT == AWT) { + GLCanvas glCanvas = (GLCanvas)glDrawable; + fboDrawable = (GLFBODrawable)glCanvas.getDelegatedDrawable(); + } else { + GLWindow glWindow = (GLWindow)glDrawable; + fboDrawable = (GLFBODrawable)glWindow.getDelegatedDrawable(); + } + + if (fboDrawable != null) { + backFBO = fboDrawable.getFBObject(GL.GL_BACK); + if (1 < numSamples) { + if (needSepFrontTex) { + // When using multisampled FBO, the back buffer is the MSAA + // surface so it cannot be read from. The sink buffer contains + // the readable 2D texture. + // In this case, we create an auxiliary "front" buffer that it is + // swapped with the sink buffer at the beginning of each frame. + // In this way, we always have a readable copy of the previous + // frame in the front texture, while the back is synchronized + // with the contents of the MSAA back buffer when requested. + if (frontFBO == null) { + // init + frontFBO = new FBObject(); + frontFBO.reset(gl, pg.width, pg.height, numSamples); + frontFBO.attachTexture2D(gl, 0, true); + sinkFBO = backFBO.getSamplingSinkFBO(); + changedFrontTex = changedBackTex = true; + } else { + // swap + FBObject temp = sinkFBO; + sinkFBO = frontFBO; + frontFBO = temp; + backFBO.setSamplingSink(sinkFBO); + changedFrontTex = changedBackTex = false; + } + backTexAttach = (FBObject.TextureAttachment) sinkFBO. + getColorbuffer(0); + frontTexAttach = (FBObject.TextureAttachment)frontFBO. + getColorbuffer(0); + } else { + changedFrontTex = changedBackTex = sinkFBO == null; + + // Default setting (to save resources): the front and back + // textures are the same. + sinkFBO = backFBO.getSamplingSinkFBO(); + backTexAttach = (FBObject.TextureAttachment) sinkFBO. + getColorbuffer(0); + frontTexAttach = backTexAttach; + } + } else { + // w/out multisampling, rendering is done on the back buffer. + frontFBO = fboDrawable.getFBObject(GL.GL_FRONT); + backTexAttach = (FBObject.TextureAttachment) backFBO.getColorbuffer(0); + frontTexAttach = (FBObject.TextureAttachment) frontFBO.getColorbuffer(0); + } + } + } + + try { + pg.parent.handleDraw(); + } catch (Exception ex) { + drawException = ex; + } + drawLatch.countDown(); + } + + @Override + public void dispose(GLAutoDrawable adrawable) { + } + + @Override + public void init(GLAutoDrawable glDrawable) { + getGL(glDrawable); + + capabilities = glDrawable.getChosenGLCapabilities(); + if (!hasFBOs()) { + throw new RuntimeException(MISSING_FBO_ERROR); + } + if (!hasShaders()) { + throw new RuntimeException(MISSING_GLSL_ERROR); + } + if (USE_JOGL_FBOLAYER && capabilities.isFBO()) { + int maxs = maxSamples(); + numSamples = PApplet.min(capabilities.getNumSamples(), maxs); + } + } + + @Override + public void reshape(GLAutoDrawable glDrawable, int x, int y, int w, int h) { + //getGL(glDrawable); + } + +// private void getGL(GLAutoDrawable glDrawable) { +// drawable = glDrawable; +// context = glDrawable.getContext(); +// glContext = context.hashCode(); +// glThread = Thread.currentThread(); +// +// gl = context.getGL(); +// gl2 = gl.getGL2ES2(); +// try { +// gl2x = gl.getGL2(); +// } catch (javax.media.opengl.GLException e) { +// gl2x = null; +// } +// try { +// gl3 = gl.getGL2GL3(); +// } catch (javax.media.opengl.GLException e) { +// gl3 = null; +// } +// } + } + */ + + /* + protected void nativeMouseEvent(com.jogamp.newt.event.MouseEvent nativeEvent, + int peAction) { + int modifiers = nativeEvent.getModifiers(); + int peModifiers = modifiers & + (InputEvent.SHIFT_MASK | + InputEvent.CTRL_MASK | + InputEvent.META_MASK | + InputEvent.ALT_MASK); + + int peButton = 0; + if ((modifiers & InputEvent.BUTTON1_MASK) != 0) { + peButton = PConstants.LEFT; + } else if ((modifiers & InputEvent.BUTTON2_MASK) != 0) { + peButton = PConstants.CENTER; + } else if ((modifiers & InputEvent.BUTTON3_MASK) != 0) { + peButton = PConstants.RIGHT; + } + + if (PApplet.platform == PConstants.MACOSX) { + //if (nativeEvent.isPopupTrigger()) { + if ((modifiers & InputEvent.CTRL_MASK) != 0) { + peButton = PConstants.RIGHT; + } + } + + int peCount = 0; + if (peAction == MouseEvent.WHEEL) { + peCount = nativeEvent.isShiftDown() ? (int)nativeEvent.getRotation()[0] : + (int)nativeEvent.getRotation()[1]; + } else { + peCount = nativeEvent.getClickCount(); + } + + MouseEvent me = new MouseEvent(nativeEvent, nativeEvent.getWhen(), + peAction, peModifiers, + nativeEvent.getX(), nativeEvent.getY(), + peButton, + peCount); + + pg.parent.postEvent(me); + } + + protected void nativeKeyEvent(com.jogamp.newt.event.KeyEvent nativeEvent, + int peAction) { + int peModifiers = nativeEvent.getModifiers() & + (InputEvent.SHIFT_MASK | + InputEvent.CTRL_MASK | + InputEvent.META_MASK | + InputEvent.ALT_MASK); + + char keyChar; + if (nativeEvent.getKeyChar() == 0) { + keyChar = PConstants.CODED; + } else { + keyChar = nativeEvent.getKeyChar(); + } + + KeyEvent ke = new KeyEvent(nativeEvent, nativeEvent.getWhen(), + peAction, peModifiers, + keyChar, + nativeEvent.getKeyCode()); + + pg.parent.postEvent(ke); + } + + protected class NEWTWindowListener implements com.jogamp.newt.event.WindowListener { + public NEWTWindowListener() { + super(); + } + @Override + public void windowGainedFocus(com.jogamp.newt.event.WindowEvent arg0) { + pg.parent.focusGained(null); + } + + @Override + public void windowLostFocus(com.jogamp.newt.event.WindowEvent arg0) { + pg.parent.focusLost(null); + } + + @Override + public void windowDestroyNotify(com.jogamp.newt.event.WindowEvent arg0) { + } + + @Override + public void windowDestroyed(com.jogamp.newt.event.WindowEvent arg0) { + } + + @Override + public void windowMoved(com.jogamp.newt.event.WindowEvent arg0) { + } + + @Override + public void windowRepaint(com.jogamp.newt.event.WindowUpdateEvent arg0) { + } + + @Override + public void windowResized(com.jogamp.newt.event.WindowEvent arg0) { } + } + + // NEWT mouse listener + protected class NEWTMouseListener extends com.jogamp.newt.event.MouseAdapter { + public NEWTMouseListener() { + super(); + } + @Override + public void mousePressed(com.jogamp.newt.event.MouseEvent e) { + nativeMouseEvent(e, MouseEvent.PRESS); + } + @Override + public void mouseReleased(com.jogamp.newt.event.MouseEvent e) { + nativeMouseEvent(e, MouseEvent.RELEASE); + } + @Override + public void mouseClicked(com.jogamp.newt.event.MouseEvent e) { + nativeMouseEvent(e, MouseEvent.CLICK); + } + @Override + public void mouseDragged(com.jogamp.newt.event.MouseEvent e) { + nativeMouseEvent(e, MouseEvent.DRAG); + } + @Override + public void mouseMoved(com.jogamp.newt.event.MouseEvent e) { + nativeMouseEvent(e, MouseEvent.MOVE); + } + @Override + public void mouseWheelMoved(com.jogamp.newt.event.MouseEvent e) { + nativeMouseEvent(e, MouseEvent.WHEEL); + } + @Override + public void mouseEntered(com.jogamp.newt.event.MouseEvent e) { + nativeMouseEvent(e, MouseEvent.ENTER); + } + @Override + public void mouseExited(com.jogamp.newt.event.MouseEvent e) { + nativeMouseEvent(e, MouseEvent.EXIT); + } + } + + // NEWT key listener + protected class NEWTKeyListener extends com.jogamp.newt.event.KeyAdapter { + public NEWTKeyListener() { + super(); + } + @Override + public void keyPressed(com.jogamp.newt.event.KeyEvent e) { + nativeKeyEvent(e, KeyEvent.PRESS); + } + @Override + public void keyReleased(com.jogamp.newt.event.KeyEvent e) { + nativeKeyEvent(e, KeyEvent.RELEASE); + } + public void keyTyped(com.jogamp.newt.event.KeyEvent e) { + nativeKeyEvent(e, KeyEvent.TYPE); + } + } +*/ + + /////////////////////////////////////////////////////////// + + // Utility functions + + + @Override + protected void enableTexturing(int target) { + if (PROFILE == 2) enable(target); + if (target == TEXTURE_2D) { + texturingTargets[0] = true; + } else if (target == TEXTURE_RECTANGLE) { + texturingTargets[1] = true; + } + } + + + @Override + protected void disableTexturing(int target) { + if (PROFILE == 2) disable(target); + if (target == TEXTURE_2D) { + texturingTargets[0] = false; + } else if (target == TEXTURE_RECTANGLE) { + texturingTargets[1] = false; + } + } + + + + @Override + protected int getFontAscent(Object font) { + return pg.getFontMetrics((Font) font).getAscent(); + } + + + @Override + protected int getFontDescent(Object font) { + return pg.getFontMetrics((Font) font).getDescent(); + } + + + @Override + protected int getTextWidth(Object font, char[] buffer, int start, int stop) { + // maybe should use one of the newer/fancier functions for this? + int length = stop - start; + FontMetrics metrics = pg.getFontMetrics((Font) font); + return metrics.charsWidth(buffer, start, length); + } + + + @Override + protected Object getDerivedFont(Object font, float size) { + return ((Font) font).deriveFont(size); + } + + + @Override + protected String[] loadVertexShader(String filename, int version) { + if (2 < PROFILE && version < 150) { + String[] fragSrc0 = pg.parent.loadStrings(filename); + return convertFragmentSource(fragSrc0, version, 150); + } else { + return pg.parent.loadStrings(filename); + } + } + + + @Override + protected String[] loadFragmentShader(String filename, int version) { + if (2 < PROFILE && version < 150) { + String[] vertSrc0 = pg.parent.loadStrings(filename); + return convertVertexSource(vertSrc0, version, 150); + } else { + return pg.parent.loadStrings(filename); + } + } + + + @Override + protected String[] loadFragmentShader(URL url, int version) { + try { + if (2 < PROFILE && version < 150) { + String[] fragSrc0 = PApplet.loadStrings(url.openStream()); + return convertFragmentSource(fragSrc0, version, 150); + } else { + return PApplet.loadStrings(url.openStream()); + } + } catch (IOException e) { + PGraphics.showException("Cannot load fragment shader " + url.getFile()); + } + return null; + } + + + @Override + protected String[] loadVertexShader(URL url, int version) { + try { + if (2 < PROFILE && version < 150) { + String[] vertSrc0 = PApplet.loadStrings(url.openStream()); + return convertVertexSource(vertSrc0, version, 150); + } else { + return PApplet.loadStrings(url.openStream()); + } + } catch (IOException e) { + PGraphics.showException("Cannot load vertex shader " + url.getFile()); + } + return null; + } + + + /////////////////////////////////////////////////////////// + + // Tessellator + + + @Override + protected Tessellator createTessellator(TessellatorCallback callback) { + return new Tessellator(callback); + } + + + protected static class Tessellator implements PGL.Tessellator { + protected GLUtessellator tess; + protected TessellatorCallback callback; + protected GLUCallback gluCallback; + + public Tessellator(TessellatorCallback callback) { + this.callback = callback; + tess = GLU.gluNewTess(); + gluCallback = new GLUCallback(); + + GLU.gluTessCallback(tess, GLU.GLU_TESS_BEGIN, gluCallback); + GLU.gluTessCallback(tess, GLU.GLU_TESS_END, gluCallback); + GLU.gluTessCallback(tess, GLU.GLU_TESS_VERTEX, gluCallback); + GLU.gluTessCallback(tess, GLU.GLU_TESS_COMBINE, gluCallback); + GLU.gluTessCallback(tess, GLU.GLU_TESS_ERROR, gluCallback); + } + + @Override + public void beginPolygon() { + GLU.gluTessBeginPolygon(tess, null); + } + + @Override + public void endPolygon() { + GLU.gluTessEndPolygon(tess); + } + + @Override + public void setWindingRule(int rule) { + GLU.gluTessProperty(tess, GLU.GLU_TESS_WINDING_RULE, rule); + } + + @Override + public void beginContour() { + GLU.gluTessBeginContour(tess); + } + + @Override + public void endContour() { + GLU.gluTessEndContour(tess); + } + + @Override + public void addVertex(double[] v) { + GLU.gluTessVertex(tess, v, 0, v); + } + + protected class GLUCallback extends GLUtessellatorCallbackAdapter { + @Override + public void begin(int type) { + callback.begin(type); + } + + @Override + public void end() { + callback.end(); + } + + @Override + public void vertex(Object data) { + callback.vertex(data); + } + + @Override + public void combine(double[] coords, Object[] data, + float[] weight, Object[] outData) { + callback.combine(coords, data, weight, outData); + } + + @Override + public void error(int errnum) { + callback.error(errnum); + } + } + } + + + @Override + protected String tessError(int err) { + return glu.gluErrorString(err); + } + + + /////////////////////////////////////////////////////////// + + // Font outline + + + static { + SHAPE_TEXT_SUPPORTED = true; + SEG_MOVETO = PathIterator.SEG_MOVETO; + SEG_LINETO = PathIterator.SEG_LINETO; + SEG_QUADTO = PathIterator.SEG_QUADTO; + SEG_CUBICTO = PathIterator.SEG_CUBICTO; + SEG_CLOSE = PathIterator.SEG_CLOSE; + } + + + @Override + protected FontOutline createFontOutline(char ch, Object font) { + return new FontOutline(ch, (Font) font); + } + + + protected class FontOutline implements PGL.FontOutline { + PathIterator iter; + + public FontOutline(char ch, Font font) { + char textArray[] = new char[] { ch }; + FontRenderContext frc = pg.getFontRenderContext(font); + GlyphVector gv = font.createGlyphVector(frc, textArray); + Shape shp = gv.getOutline(); + iter = shp.getPathIterator(null); + } + + public boolean isDone() { + return iter.isDone(); + } + + public int currentSegment(float coords[]) { + return iter.currentSegment(coords); + } + + public void next() { + iter.next(); + } + } + + + /////////////////////////////////////////////////////////// + + // Constants + + static { + FALSE = GL.GL_FALSE; + TRUE = GL.GL_TRUE; + + INT = GL2ES2.GL_INT; + BYTE = GL.GL_BYTE; + SHORT = GL.GL_SHORT; + FLOAT = GL.GL_FLOAT; + BOOL = GL2ES2.GL_BOOL; + UNSIGNED_INT = GL.GL_UNSIGNED_INT; + UNSIGNED_BYTE = GL.GL_UNSIGNED_BYTE; + UNSIGNED_SHORT = GL.GL_UNSIGNED_SHORT; + + RGB = GL.GL_RGB; + RGBA = GL.GL_RGBA; + ALPHA = GL.GL_ALPHA; + LUMINANCE = GL.GL_LUMINANCE; + LUMINANCE_ALPHA = GL.GL_LUMINANCE_ALPHA; + + UNSIGNED_SHORT_5_6_5 = GL.GL_UNSIGNED_SHORT_5_6_5; + UNSIGNED_SHORT_4_4_4_4 = GL.GL_UNSIGNED_SHORT_4_4_4_4; + UNSIGNED_SHORT_5_5_5_1 = GL.GL_UNSIGNED_SHORT_5_5_5_1; + + RGBA4 = GL.GL_RGBA4; + RGB5_A1 = GL.GL_RGB5_A1; + RGB565 = GL.GL_RGB565; + RGB8 = GL.GL_RGB8; + RGBA8 = GL.GL_RGBA8; + ALPHA8 = GL.GL_ALPHA8; + + READ_ONLY = GL2GL3.GL_READ_ONLY; + WRITE_ONLY = GL.GL_WRITE_ONLY; + READ_WRITE = GL2GL3.GL_READ_WRITE; + + TESS_WINDING_NONZERO = GLU.GLU_TESS_WINDING_NONZERO; + TESS_WINDING_ODD = GLU.GLU_TESS_WINDING_ODD; + + GENERATE_MIPMAP_HINT = GL.GL_GENERATE_MIPMAP_HINT; + FASTEST = GL.GL_FASTEST; + NICEST = GL.GL_NICEST; + DONT_CARE = GL.GL_DONT_CARE; + + VENDOR = GL.GL_VENDOR; + RENDERER = GL.GL_RENDERER; + VERSION = GL.GL_VERSION; + EXTENSIONS = GL.GL_EXTENSIONS; + SHADING_LANGUAGE_VERSION = GL2ES2.GL_SHADING_LANGUAGE_VERSION; + + MAX_SAMPLES = GL2ES3.GL_MAX_SAMPLES; + SAMPLES = GL.GL_SAMPLES; + + ALIASED_LINE_WIDTH_RANGE = GL.GL_ALIASED_LINE_WIDTH_RANGE; + ALIASED_POINT_SIZE_RANGE = GL.GL_ALIASED_POINT_SIZE_RANGE; + + DEPTH_BITS = GL.GL_DEPTH_BITS; + STENCIL_BITS = GL.GL_STENCIL_BITS; + + CCW = GL.GL_CCW; + CW = GL.GL_CW; + + VIEWPORT = GL.GL_VIEWPORT; + + ARRAY_BUFFER = GL.GL_ARRAY_BUFFER; + ELEMENT_ARRAY_BUFFER = GL.GL_ELEMENT_ARRAY_BUFFER; + + MAX_VERTEX_ATTRIBS = GL2ES2.GL_MAX_VERTEX_ATTRIBS; + + STATIC_DRAW = GL.GL_STATIC_DRAW; + DYNAMIC_DRAW = GL.GL_DYNAMIC_DRAW; + STREAM_DRAW = GL2ES2.GL_STREAM_DRAW; + + BUFFER_SIZE = GL.GL_BUFFER_SIZE; + BUFFER_USAGE = GL.GL_BUFFER_USAGE; + + POINTS = GL.GL_POINTS; + LINE_STRIP = GL.GL_LINE_STRIP; + LINE_LOOP = GL.GL_LINE_LOOP; + LINES = GL.GL_LINES; + TRIANGLE_FAN = GL.GL_TRIANGLE_FAN; + TRIANGLE_STRIP = GL.GL_TRIANGLE_STRIP; + TRIANGLES = GL.GL_TRIANGLES; + + CULL_FACE = GL.GL_CULL_FACE; + FRONT = GL.GL_FRONT; + BACK = GL.GL_BACK; + FRONT_AND_BACK = GL.GL_FRONT_AND_BACK; + + POLYGON_OFFSET_FILL = GL.GL_POLYGON_OFFSET_FILL; + + UNPACK_ALIGNMENT = GL.GL_UNPACK_ALIGNMENT; + PACK_ALIGNMENT = GL.GL_PACK_ALIGNMENT; + + TEXTURE_2D = GL.GL_TEXTURE_2D; + TEXTURE_RECTANGLE = GL2GL3.GL_TEXTURE_RECTANGLE; + + TEXTURE_BINDING_2D = GL.GL_TEXTURE_BINDING_2D; + TEXTURE_BINDING_RECTANGLE = GL2GL3.GL_TEXTURE_BINDING_RECTANGLE; + + MAX_TEXTURE_SIZE = GL.GL_MAX_TEXTURE_SIZE; + TEXTURE_MAX_ANISOTROPY = GL.GL_TEXTURE_MAX_ANISOTROPY_EXT; + MAX_TEXTURE_MAX_ANISOTROPY = GL.GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT; + + MAX_VERTEX_TEXTURE_IMAGE_UNITS = GL2ES2.GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS; + MAX_TEXTURE_IMAGE_UNITS = GL2ES2.GL_MAX_TEXTURE_IMAGE_UNITS; + MAX_COMBINED_TEXTURE_IMAGE_UNITS = GL2ES2.GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS; + + NUM_COMPRESSED_TEXTURE_FORMATS = GL.GL_NUM_COMPRESSED_TEXTURE_FORMATS; + COMPRESSED_TEXTURE_FORMATS = GL.GL_COMPRESSED_TEXTURE_FORMATS; + + NEAREST = GL.GL_NEAREST; + LINEAR = GL.GL_LINEAR; + LINEAR_MIPMAP_NEAREST = GL.GL_LINEAR_MIPMAP_NEAREST; + LINEAR_MIPMAP_LINEAR = GL.GL_LINEAR_MIPMAP_LINEAR; + + CLAMP_TO_EDGE = GL.GL_CLAMP_TO_EDGE; + REPEAT = GL.GL_REPEAT; + + TEXTURE0 = GL.GL_TEXTURE0; + TEXTURE1 = GL.GL_TEXTURE1; + TEXTURE2 = GL.GL_TEXTURE2; + TEXTURE3 = GL.GL_TEXTURE3; + TEXTURE_MIN_FILTER = GL.GL_TEXTURE_MIN_FILTER; + TEXTURE_MAG_FILTER = GL.GL_TEXTURE_MAG_FILTER; + TEXTURE_WRAP_S = GL.GL_TEXTURE_WRAP_S; + TEXTURE_WRAP_T = GL.GL_TEXTURE_WRAP_T; + TEXTURE_WRAP_R = GL2ES2.GL_TEXTURE_WRAP_R; + + TEXTURE_CUBE_MAP = GL.GL_TEXTURE_CUBE_MAP; + TEXTURE_CUBE_MAP_POSITIVE_X = GL.GL_TEXTURE_CUBE_MAP_POSITIVE_X; + TEXTURE_CUBE_MAP_POSITIVE_Y = GL.GL_TEXTURE_CUBE_MAP_POSITIVE_Y; + TEXTURE_CUBE_MAP_POSITIVE_Z = GL.GL_TEXTURE_CUBE_MAP_POSITIVE_Z; + TEXTURE_CUBE_MAP_NEGATIVE_X = GL.GL_TEXTURE_CUBE_MAP_NEGATIVE_X; + TEXTURE_CUBE_MAP_NEGATIVE_Y = GL.GL_TEXTURE_CUBE_MAP_NEGATIVE_Y; + TEXTURE_CUBE_MAP_NEGATIVE_Z = GL.GL_TEXTURE_CUBE_MAP_NEGATIVE_Z; + + VERTEX_SHADER = GL2ES2.GL_VERTEX_SHADER; + FRAGMENT_SHADER = GL2ES2.GL_FRAGMENT_SHADER; + INFO_LOG_LENGTH = GL2ES2.GL_INFO_LOG_LENGTH; + SHADER_SOURCE_LENGTH = GL2ES2.GL_SHADER_SOURCE_LENGTH; + COMPILE_STATUS = GL2ES2.GL_COMPILE_STATUS; + LINK_STATUS = GL2ES2.GL_LINK_STATUS; + VALIDATE_STATUS = GL2ES2.GL_VALIDATE_STATUS; + SHADER_TYPE = GL2ES2.GL_SHADER_TYPE; + DELETE_STATUS = GL2ES2.GL_DELETE_STATUS; + + FLOAT_VEC2 = GL2ES2.GL_FLOAT_VEC2; + FLOAT_VEC3 = GL2ES2.GL_FLOAT_VEC3; + FLOAT_VEC4 = GL2ES2.GL_FLOAT_VEC4; + FLOAT_MAT2 = GL2ES2.GL_FLOAT_MAT2; + FLOAT_MAT3 = GL2ES2.GL_FLOAT_MAT3; + FLOAT_MAT4 = GL2ES2.GL_FLOAT_MAT4; + INT_VEC2 = GL2ES2.GL_INT_VEC2; + INT_VEC3 = GL2ES2.GL_INT_VEC3; + INT_VEC4 = GL2ES2.GL_INT_VEC4; + BOOL_VEC2 = GL2ES2.GL_BOOL_VEC2; + BOOL_VEC3 = GL2ES2.GL_BOOL_VEC3; + BOOL_VEC4 = GL2ES2.GL_BOOL_VEC4; + SAMPLER_2D = GL2ES2.GL_SAMPLER_2D; + SAMPLER_CUBE = GL2ES2.GL_SAMPLER_CUBE; + + LOW_FLOAT = GL2ES2.GL_LOW_FLOAT; + MEDIUM_FLOAT = GL2ES2.GL_MEDIUM_FLOAT; + HIGH_FLOAT = GL2ES2.GL_HIGH_FLOAT; + LOW_INT = GL2ES2.GL_LOW_INT; + MEDIUM_INT = GL2ES2.GL_MEDIUM_INT; + HIGH_INT = GL2ES2.GL_HIGH_INT; + + CURRENT_VERTEX_ATTRIB = GL2ES2.GL_CURRENT_VERTEX_ATTRIB; + + VERTEX_ATTRIB_ARRAY_BUFFER_BINDING = GL2ES2.GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING; + VERTEX_ATTRIB_ARRAY_ENABLED = GL2ES2.GL_VERTEX_ATTRIB_ARRAY_ENABLED; + VERTEX_ATTRIB_ARRAY_SIZE = GL2ES2.GL_VERTEX_ATTRIB_ARRAY_SIZE; + VERTEX_ATTRIB_ARRAY_STRIDE = GL2ES2.GL_VERTEX_ATTRIB_ARRAY_STRIDE; + VERTEX_ATTRIB_ARRAY_TYPE = GL2ES2.GL_VERTEX_ATTRIB_ARRAY_TYPE; + VERTEX_ATTRIB_ARRAY_NORMALIZED = GL2ES2.GL_VERTEX_ATTRIB_ARRAY_NORMALIZED; + VERTEX_ATTRIB_ARRAY_POINTER = GL2ES2.GL_VERTEX_ATTRIB_ARRAY_POINTER; + + BLEND = GL.GL_BLEND; + ONE = GL.GL_ONE; + ZERO = GL.GL_ZERO; + SRC_ALPHA = GL.GL_SRC_ALPHA; + DST_ALPHA = GL.GL_DST_ALPHA; + ONE_MINUS_SRC_ALPHA = GL.GL_ONE_MINUS_SRC_ALPHA; + ONE_MINUS_DST_COLOR = GL.GL_ONE_MINUS_DST_COLOR; + ONE_MINUS_SRC_COLOR = GL.GL_ONE_MINUS_SRC_COLOR; + DST_COLOR = GL.GL_DST_COLOR; + SRC_COLOR = GL.GL_SRC_COLOR; + + SAMPLE_ALPHA_TO_COVERAGE = GL.GL_SAMPLE_ALPHA_TO_COVERAGE; + SAMPLE_COVERAGE = GL.GL_SAMPLE_COVERAGE; + + KEEP = GL.GL_KEEP; + REPLACE = GL.GL_REPLACE; + INCR = GL.GL_INCR; + DECR = GL.GL_DECR; + INVERT = GL.GL_INVERT; + INCR_WRAP = GL.GL_INCR_WRAP; + DECR_WRAP = GL.GL_DECR_WRAP; + NEVER = GL.GL_NEVER; + ALWAYS = GL.GL_ALWAYS; + + EQUAL = GL.GL_EQUAL; + LESS = GL.GL_LESS; + LEQUAL = GL.GL_LEQUAL; + GREATER = GL.GL_GREATER; + GEQUAL = GL.GL_GEQUAL; + NOTEQUAL = GL.GL_NOTEQUAL; + + FUNC_ADD = GL.GL_FUNC_ADD; + FUNC_MIN = GL2ES3.GL_MIN; + FUNC_MAX = GL2ES3.GL_MAX; + FUNC_REVERSE_SUBTRACT = GL.GL_FUNC_REVERSE_SUBTRACT; + FUNC_SUBTRACT = GL.GL_FUNC_SUBTRACT; + + DITHER = GL.GL_DITHER; + + CONSTANT_COLOR = GL2ES2.GL_CONSTANT_COLOR; + CONSTANT_ALPHA = GL2ES2.GL_CONSTANT_ALPHA; + ONE_MINUS_CONSTANT_COLOR = GL2ES2.GL_ONE_MINUS_CONSTANT_COLOR; + ONE_MINUS_CONSTANT_ALPHA = GL2ES2.GL_ONE_MINUS_CONSTANT_ALPHA; + SRC_ALPHA_SATURATE = GL.GL_SRC_ALPHA_SATURATE; + + SCISSOR_TEST = GL.GL_SCISSOR_TEST; + STENCIL_TEST = GL.GL_STENCIL_TEST; + DEPTH_TEST = GL.GL_DEPTH_TEST; + DEPTH_WRITEMASK = GL.GL_DEPTH_WRITEMASK; + ALPHA_TEST = GL2ES1.GL_ALPHA_TEST; + + COLOR_BUFFER_BIT = GL.GL_COLOR_BUFFER_BIT; + DEPTH_BUFFER_BIT = GL.GL_DEPTH_BUFFER_BIT; + STENCIL_BUFFER_BIT = GL.GL_STENCIL_BUFFER_BIT; + + FRAMEBUFFER = GL.GL_FRAMEBUFFER; + COLOR_ATTACHMENT0 = GL.GL_COLOR_ATTACHMENT0; + COLOR_ATTACHMENT1 = GL2ES2.GL_COLOR_ATTACHMENT1; + COLOR_ATTACHMENT2 = GL2ES2.GL_COLOR_ATTACHMENT2; + COLOR_ATTACHMENT3 = GL2ES2.GL_COLOR_ATTACHMENT3; + RENDERBUFFER = GL.GL_RENDERBUFFER; + DEPTH_ATTACHMENT = GL.GL_DEPTH_ATTACHMENT; + STENCIL_ATTACHMENT = GL.GL_STENCIL_ATTACHMENT; + READ_FRAMEBUFFER = GL2ES3.GL_READ_FRAMEBUFFER; + DRAW_FRAMEBUFFER = GL2ES3.GL_DRAW_FRAMEBUFFER; + + RGBA8 = GL.GL_RGBA8; + DEPTH24_STENCIL8 = GL.GL_DEPTH24_STENCIL8; + + DEPTH_COMPONENT = GL2ES2.GL_DEPTH_COMPONENT; + DEPTH_COMPONENT16 = GL.GL_DEPTH_COMPONENT16; + DEPTH_COMPONENT24 = GL.GL_DEPTH_COMPONENT24; + DEPTH_COMPONENT32 = GL.GL_DEPTH_COMPONENT32; + + STENCIL_INDEX = GL2ES2.GL_STENCIL_INDEX; + STENCIL_INDEX1 = GL.GL_STENCIL_INDEX1; + STENCIL_INDEX4 = GL.GL_STENCIL_INDEX4; + STENCIL_INDEX8 = GL.GL_STENCIL_INDEX8; + + DEPTH_STENCIL = GL.GL_DEPTH_STENCIL; + + FRAMEBUFFER_COMPLETE = GL.GL_FRAMEBUFFER_COMPLETE; + FRAMEBUFFER_INCOMPLETE_ATTACHMENT = GL.GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT; + FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT = GL.GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT; + FRAMEBUFFER_INCOMPLETE_DIMENSIONS = GL.GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS; + FRAMEBUFFER_INCOMPLETE_FORMATS = GL.GL_FRAMEBUFFER_INCOMPLETE_FORMATS; + FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER = GL2GL3.GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER; + FRAMEBUFFER_INCOMPLETE_READ_BUFFER = GL2GL3.GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER; + FRAMEBUFFER_UNSUPPORTED = GL.GL_FRAMEBUFFER_UNSUPPORTED; + + FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE = GL.GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE; + FRAMEBUFFER_ATTACHMENT_OBJECT_NAME = GL.GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME; + FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL = GL.GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL; + FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE = GL.GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE; + + RENDERBUFFER_WIDTH = GL.GL_RENDERBUFFER_WIDTH; + RENDERBUFFER_HEIGHT = GL.GL_RENDERBUFFER_HEIGHT; + RENDERBUFFER_RED_SIZE = GL.GL_RENDERBUFFER_RED_SIZE; + RENDERBUFFER_GREEN_SIZE = GL.GL_RENDERBUFFER_GREEN_SIZE; + RENDERBUFFER_BLUE_SIZE = GL.GL_RENDERBUFFER_BLUE_SIZE; + RENDERBUFFER_ALPHA_SIZE = GL.GL_RENDERBUFFER_ALPHA_SIZE; + RENDERBUFFER_DEPTH_SIZE = GL.GL_RENDERBUFFER_DEPTH_SIZE; + RENDERBUFFER_STENCIL_SIZE = GL.GL_RENDERBUFFER_STENCIL_SIZE; + RENDERBUFFER_INTERNAL_FORMAT = GL.GL_RENDERBUFFER_INTERNAL_FORMAT; + + MULTISAMPLE = GL.GL_MULTISAMPLE; + POINT_SMOOTH = GL2ES1.GL_POINT_SMOOTH; + LINE_SMOOTH = GL.GL_LINE_SMOOTH; + POLYGON_SMOOTH = GL2GL3.GL_POLYGON_SMOOTH; + } + + /////////////////////////////////////////////////////////// + + // Special Functions + + @Override + public void flush() { + gl.glFlush(); + } + + @Override + public void finish() { + gl.glFinish(); + } + + @Override + public void hint(int target, int hint) { + gl.glHint(target, hint); + } + + /////////////////////////////////////////////////////////// + + // State and State Requests + + @Override + public void enable(int value) { + if (-1 < value) { + gl.glEnable(value); + } + } + + @Override + public void disable(int value) { + if (-1 < value) { + gl.glDisable(value); + } + } + + @Override + public void getBooleanv(int value, IntBuffer data) { + if (-1 < value) { + if (byteBuffer.capacity() < data.capacity()) { + byteBuffer = allocateDirectByteBuffer(data.capacity()); + } + gl.glGetBooleanv(value, byteBuffer); + for (int i = 0; i < data.capacity(); i++) { + data.put(i, byteBuffer.get(i)); + } + } else { + fillIntBuffer(data, 0, data.capacity() - 1, 0); + } + } + + @Override + public void getIntegerv(int value, IntBuffer data) { + if (-1 < value) { + gl.glGetIntegerv(value, data); + } else { + fillIntBuffer(data, 0, data.capacity() - 1, 0); + } + } + + @Override + public void getFloatv(int value, FloatBuffer data) { + if (-1 < value) { + gl.glGetFloatv(value, data); + } else { + fillFloatBuffer(data, 0, data.capacity() - 1, 0); + } + } + + @Override + public boolean isEnabled(int value) { + return gl.glIsEnabled(value); + } + + @Override + public String getString(int name) { + return gl.glGetString(name); + } + + /////////////////////////////////////////////////////////// + + // Error Handling + + @Override + public int getError() { + return gl.glGetError(); + } + + @Override + public String errorString(int err) { + return glu.gluErrorString(err); + } + + ////////////////////////////////////////////////////////////////////////////// + + // Buffer Objects + + @Override + public void genBuffers(int n, IntBuffer buffers) { + gl.glGenBuffers(n, buffers); + } + + @Override + public void deleteBuffers(int n, IntBuffer buffers) { + gl.glDeleteBuffers(n, buffers); + } + + @Override + public void bindBuffer(int target, int buffer) { + gl.glBindBuffer(target, buffer); + } + + @Override + public void bufferData(int target, int size, Buffer data, int usage) { + gl.glBufferData(target, size, data, usage); + } + + @Override + public void bufferSubData(int target, int offset, int size, Buffer data) { + gl.glBufferSubData(target, offset, size, data); + } + + @Override + public void isBuffer(int buffer) { + gl.glIsBuffer(buffer); + } + + @Override + public void getBufferParameteriv(int target, int value, IntBuffer data) { + gl.glGetBufferParameteriv(target, value, data); + } + + @Override + public ByteBuffer mapBuffer(int target, int access) { + return gl2.glMapBuffer(target, access); + } + + @Override + public ByteBuffer mapBufferRange(int target, int offset, int length, int access) { + if (gl2x != null) { + return gl2x.glMapBufferRange(target, offset, length, access); + } else if (gl3 != null) { + return gl3.glMapBufferRange(target, offset, length, access); + } else { + throw new RuntimeException(String.format(MISSING_GLFUNC_ERROR, "glMapBufferRange()")); + } + } + + @Override + public void unmapBuffer(int target) { + gl2.glUnmapBuffer(target); + } + + ////////////////////////////////////////////////////////////////////////////// + + // Viewport and Clipping + + @Override + public void depthRangef(float n, float f) { + gl.glDepthRangef(n, f); + } + + @Override + public void viewport(int x, int y, int w, int h) { + gl.glViewport(pixel_scale * x, pixel_scale * y, pixel_scale * w, pixel_scale * h); + } + + ////////////////////////////////////////////////////////////////////////////// + + // Reading Pixels + + @Override + protected void readPixelsImpl(int x, int y, int width, int height, int format, int type, Buffer buffer) { + gl.glReadPixels(x, y, width, height, format, type, buffer); + } + + ////////////////////////////////////////////////////////////////////////////// + + // Vertices + + @Override + public void vertexAttrib1f(int index, float value) { + gl2.glVertexAttrib1f(index, value); + } + + @Override + public void vertexAttrib2f(int index, float value0, float value1) { + gl2.glVertexAttrib2f(index, value0, value1); + } + + @Override + public void vertexAttrib3f(int index, float value0, float value1, float value2) { + gl2.glVertexAttrib3f(index, value0, value1, value2); + } + + @Override + public void vertexAttrib4f(int index, float value0, float value1, float value2, float value3) { + gl2.glVertexAttrib4f(index, value0, value1, value2, value3); + } + + @Override + public void vertexAttrib1fv(int index, FloatBuffer values) { + gl2.glVertexAttrib1fv(index, values); + } + + @Override + public void vertexAttrib2fv(int index, FloatBuffer values) { + gl2.glVertexAttrib2fv(index, values); + } + + @Override + public void vertexAttrib3fv(int index, FloatBuffer values) { + gl2.glVertexAttrib3fv(index, values); + } + + @Override + public void vertexAttri4fv(int index, FloatBuffer values) { + gl2.glVertexAttrib4fv(index, values); + } + + @Override + public void vertexAttribPointer(int index, int size, int type, boolean normalized, int stride, int offset) { + gl2.glVertexAttribPointer(index, size, type, normalized, stride, offset); + } + + @Override + public void vertexAttribPointer(int index, int size, int type, boolean normalized, int stride, Buffer data) { + if (gl2x != null) { + gl2x.glVertexAttribPointer(index, size, type, normalized, stride, data); + } else { + throw new RuntimeException(String.format(MISSING_GLFUNC_ERROR, "glVertexAttribPointer()")); + } + } + + @Override + public void enableVertexAttribArray(int index) { + gl2.glEnableVertexAttribArray(index); + } + + @Override + public void disableVertexAttribArray(int index) { + gl2.glDisableVertexAttribArray(index); + } + + @Override + public void drawArrays(int mode, int first, int count) { + gl.glDrawArrays(mode, first, count); + } + + @Override + public void drawElements(int mode, int count, int type, int offset) { + gl.glDrawElements(mode, count, type, offset); + } + + @Override + public void drawElements(int mode, int count, int type, Buffer indices) { + if (gl2x != null) { + gl2x.glDrawElements(mode, count, type, indices); + } else { + throw new RuntimeException(String.format(MISSING_GLFUNC_ERROR, "glDrawElements()")); + } + } + + ////////////////////////////////////////////////////////////////////////////// + + // Rasterization + + @Override + public void lineWidth(float width) { + gl.glLineWidth(width); + } + + @Override + public void frontFace(int dir) { + gl.glFrontFace(dir); + } + + @Override + public void cullFace(int mode) { + gl.glCullFace(mode); + } + + @Override + public void polygonOffset(float factor, float units) { + gl.glPolygonOffset(factor, units); + } + + ////////////////////////////////////////////////////////////////////////////// + + // Pixel Rectangles + + @Override + public void pixelStorei(int pname, int param) { + gl.glPixelStorei(pname, param); + } + + /////////////////////////////////////////////////////////// + + // Texturing + + @Override + public void texImage2D(int target, int level, int internalFormat, int width, int height, int border, int format, int type, Buffer data) { + gl.glTexImage2D(target, level, internalFormat, width, height, border, format, type, data); + } + + @Override + public void copyTexImage2D(int target, int level, int internalFormat, int x, int y, int width, int height, int border) { + gl.glCopyTexImage2D(target, level, internalFormat, x, y, width, height, border); + } + + @Override + public void texSubImage2D(int target, int level, int xOffset, int yOffset, int width, int height, int format, int type, Buffer data) { + gl.glTexSubImage2D(target, level, xOffset, yOffset, width, height, format, type, data); + } + + @Override + public void copyTexSubImage2D(int target, int level, int xOffset, int yOffset, int x, int y, int width, int height) { + gl.glCopyTexSubImage2D(target, level, x, y, xOffset, yOffset, width, height); + } + + @Override + public void compressedTexImage2D(int target, int level, int internalFormat, int width, int height, int border, int imageSize, Buffer data) { + gl.glCompressedTexImage2D(target, level, internalFormat, width, height, border, imageSize, data); + } + + @Override + public void compressedTexSubImage2D(int target, int level, int xOffset, int yOffset, int width, int height, int format, int imageSize, Buffer data) { + gl.glCompressedTexSubImage2D(target, level, xOffset, yOffset, width, height, format, imageSize, data); + } + + @Override + public void texParameteri(int target, int pname, int param) { + gl.glTexParameteri(target, pname, param); + } + + @Override + public void texParameterf(int target, int pname, float param) { + gl.glTexParameterf(target, pname, param); + } + + @Override + public void texParameteriv(int target, int pname, IntBuffer params) { + gl.glTexParameteriv(target, pname, params); + } + + @Override + public void texParameterfv(int target, int pname, FloatBuffer params) { + gl.glTexParameterfv(target, pname, params); + } + + @Override + public void generateMipmap(int target) { + gl.glGenerateMipmap(target); + } + + @Override + public void genTextures(int n, IntBuffer textures) { + gl.glGenTextures(n, textures); + } + + @Override + public void deleteTextures(int n, IntBuffer textures) { + gl.glDeleteTextures(n, textures); + } + + @Override + public void getTexParameteriv(int target, int pname, IntBuffer params) { + gl.glGetTexParameteriv(target, pname, params); + } + + @Override + public void getTexParameterfv(int target, int pname, FloatBuffer params) { + gl.glGetTexParameterfv(target, pname, params); + } + + @Override + public boolean isTexture(int texture) { + return gl.glIsTexture(texture); + } + + @Override + protected void activeTextureImpl(int texture) { + gl.glActiveTexture(texture); + } + + @Override + protected void bindTextureImpl(int target, int texture) { + gl.glBindTexture(target, texture); + } + + /////////////////////////////////////////////////////////// + + // Shaders and Programs + + @Override + public int createShader(int type) { + return gl2.glCreateShader(type); + } + + @Override + public void shaderSource(int shader, String source) { + gl2.glShaderSource(shader, 1, new String[] { source }, (int[]) null, 0); + } + + @Override + public void compileShader(int shader) { + gl2.glCompileShader(shader); + } + + @Override + public void releaseShaderCompiler() { + gl2.glReleaseShaderCompiler(); + } + + @Override + public void deleteShader(int shader) { + gl2.glDeleteShader(shader); + } + + @Override + public void shaderBinary(int count, IntBuffer shaders, int binaryFormat, Buffer binary, int length) { + gl2.glShaderBinary(count, shaders, binaryFormat, binary, length); + } + + @Override + public int createProgram() { + return gl2.glCreateProgram(); + } + + @Override + public void attachShader(int program, int shader) { + gl2.glAttachShader(program, shader); + } + + @Override + public void detachShader(int program, int shader) { + gl2.glDetachShader(program, shader); + } + + @Override + public void linkProgram(int program) { + gl2.glLinkProgram(program); + } + + @Override + public void useProgram(int program) { + gl2.glUseProgram(program); + } + + @Override + public void deleteProgram(int program) { + gl2.glDeleteProgram(program); + } + + @Override + public String getActiveAttrib(int program, int index, IntBuffer size, IntBuffer type) { + int[] tmp = {0, 0, 0}; + byte[] namebuf = new byte[1024]; + gl2.glGetActiveAttrib(program, index, 1024, tmp, 0, tmp, 1, tmp, 2, namebuf, 0); + size.put(tmp[1]); + type.put(tmp[2]); + String name = new String(namebuf, 0, tmp[0]); + return name; + } + + @Override + public int getAttribLocation(int program, String name) { + return gl2.glGetAttribLocation(program, name); + } + + @Override + public void bindAttribLocation(int program, int index, String name) { + gl2.glBindAttribLocation(program, index, name); + } + + @Override + public int getUniformLocation(int program, String name) { + return gl2.glGetUniformLocation(program, name); + } + + @Override + public String getActiveUniform(int program, int index, IntBuffer size, IntBuffer type) { + int[] tmp= {0, 0, 0}; + byte[] namebuf = new byte[1024]; + gl2.glGetActiveUniform(program, index, 1024, tmp, 0, tmp, 1, tmp, 2, namebuf, 0); + size.put(tmp[1]); + type.put(tmp[2]); + String name = new String(namebuf, 0, tmp[0]); + return name; + } + + @Override + public void uniform1i(int location, int value) { + gl2.glUniform1i(location, value); + } + + @Override + public void uniform2i(int location, int value0, int value1) { + gl2.glUniform2i(location, value0, value1); + } + + @Override + public void uniform3i(int location, int value0, int value1, int value2) { + gl2.glUniform3i(location, value0, value1, value2); + } + + @Override + public void uniform4i(int location, int value0, int value1, int value2, int value3) { + gl2.glUniform4i(location, value0, value1, value2, value3); + } + + @Override + public void uniform1f(int location, float value) { + gl2.glUniform1f(location, value); + } + + @Override + public void uniform2f(int location, float value0, float value1) { + gl2.glUniform2f(location, value0, value1); + } + + @Override + public void uniform3f(int location, float value0, float value1, float value2) { + gl2.glUniform3f(location, value0, value1, value2); + } + + @Override + public void uniform4f(int location, float value0, float value1, float value2, float value3) { + gl2.glUniform4f(location, value0, value1, value2, value3); + } + + @Override + public void uniform1iv(int location, int count, IntBuffer v) { + gl2.glUniform1iv(location, count, v); + } + + @Override + public void uniform2iv(int location, int count, IntBuffer v) { + gl2.glUniform2iv(location, count, v); + } + + @Override + public void uniform3iv(int location, int count, IntBuffer v) { + gl2.glUniform3iv(location, count, v); + } + + @Override + public void uniform4iv(int location, int count, IntBuffer v) { + gl2.glUniform4iv(location, count, v); + } + + @Override + public void uniform1fv(int location, int count, FloatBuffer v) { + gl2.glUniform1fv(location, count, v); + } + + @Override + public void uniform2fv(int location, int count, FloatBuffer v) { + gl2.glUniform2fv(location, count, v); + } + + @Override + public void uniform3fv(int location, int count, FloatBuffer v) { + gl2.glUniform3fv(location, count, v); + } + + @Override + public void uniform4fv(int location, int count, FloatBuffer v) { + gl2.glUniform4fv(location, count, v); + } + + @Override + public void uniformMatrix2fv(int location, int count, boolean transpose, FloatBuffer mat) { + gl2.glUniformMatrix2fv(location, count, transpose, mat); + } + + @Override + public void uniformMatrix3fv(int location, int count, boolean transpose, FloatBuffer mat) { + gl2.glUniformMatrix3fv(location, count, transpose, mat); + } + + @Override + public void uniformMatrix4fv(int location, int count, boolean transpose, FloatBuffer mat) { + gl2.glUniformMatrix4fv(location, count, transpose, mat); + } + + @Override + public void validateProgram(int program) { + gl2.glValidateProgram(program); + } + + @Override + public boolean isShader(int shader) { + return gl2.glIsShader(shader); + } + + @Override + public void getShaderiv(int shader, int pname, IntBuffer params) { + gl2.glGetShaderiv(shader, pname, params); + } + + @Override + public void getAttachedShaders(int program, int maxCount, IntBuffer count, IntBuffer shaders) { + gl2.glGetAttachedShaders(program, maxCount, count, shaders); + } + + @Override + public String getShaderInfoLog(int shader) { + int[] val = { 0 }; + gl2.glGetShaderiv(shader, GL2ES2.GL_INFO_LOG_LENGTH, val, 0); + int length = val[0]; + + byte[] log = new byte[length]; + gl2.glGetShaderInfoLog(shader, length, val, 0, log, 0); + return new String(log); + } + + @Override + public String getShaderSource(int shader) { + int[] len = {0}; + byte[] buf = new byte[1024]; + gl2.glGetShaderSource(shader, 1024, len, 0, buf, 0); + return new String(buf, 0, len[0]); + } + + @Override + public void getShaderPrecisionFormat(int shaderType, int precisionType, IntBuffer range, IntBuffer precision) { + gl2.glGetShaderPrecisionFormat(shaderType, precisionType, range, precision); + } + + @Override + public void getVertexAttribfv(int index, int pname, FloatBuffer params) { + gl2.glGetVertexAttribfv(index, pname, params); + } + + @Override + public void getVertexAttribiv(int index, int pname, IntBuffer params) { + gl2.glGetVertexAttribiv(index, pname, params); + } + + @Override + public void getVertexAttribPointerv(int index, int pname, ByteBuffer data) { + throw new RuntimeException(String.format(MISSING_GLFUNC_ERROR, "glGetVertexAttribPointerv()")); + } + + @Override + public void getUniformfv(int program, int location, FloatBuffer params) { + gl2.glGetUniformfv(program, location, params); + } + + @Override + public void getUniformiv(int program, int location, IntBuffer params) { + gl2.glGetUniformiv(program, location, params); + } + + @Override + public boolean isProgram(int program) { + return gl2.glIsProgram(program); + } + + @Override + public void getProgramiv(int program, int pname, IntBuffer params) { + gl2.glGetProgramiv(program, pname, params); + } + + @Override + public String getProgramInfoLog(int program) { + int[] val = { 0 }; + gl2.glGetShaderiv(program, GL2ES2.GL_INFO_LOG_LENGTH, val, 0); + int length = val[0]; + + if (0 < length) { + byte[] log = new byte[length]; + gl2.glGetProgramInfoLog(program, length, val, 0, log, 0); + return new String(log); + } else { + return "Unknow error"; + } + } + + /////////////////////////////////////////////////////////// + + // Per-Fragment Operations + + @Override + public void scissor(int x, int y, int w, int h) { + gl.glScissor(pixel_scale * x, pixel_scale * y, pixel_scale * w, pixel_scale * h); + } + + @Override + public void sampleCoverage(float value, boolean invert) { + gl2.glSampleCoverage(value, invert); + } + + @Override + public void stencilFunc(int func, int ref, int mask) { + gl2.glStencilFunc(func, ref, mask); + } + + @Override + public void stencilFuncSeparate(int face, int func, int ref, int mask) { + gl2.glStencilFuncSeparate(face, func, ref, mask); + } + + @Override + public void stencilOp(int sfail, int dpfail, int dppass) { + gl2.glStencilOp(sfail, dpfail, dppass); + } + + @Override + public void stencilOpSeparate(int face, int sfail, int dpfail, int dppass) { + gl2.glStencilOpSeparate(face, sfail, dpfail, dppass); + } + + @Override + public void depthFunc(int func) { + gl.glDepthFunc(func); + } + + @Override + public void blendEquation(int mode) { + gl.glBlendEquation(mode); + } + + @Override + public void blendEquationSeparate(int modeRGB, int modeAlpha) { + gl.glBlendEquationSeparate(modeRGB, modeAlpha); + } + + @Override + public void blendFunc(int src, int dst) { + gl.glBlendFunc(src, dst); + } + + @Override + public void blendFuncSeparate(int srcRGB, int dstRGB, int srcAlpha, int dstAlpha) { + gl.glBlendFuncSeparate(srcRGB, dstRGB, srcAlpha, dstAlpha); + } + + @Override + public void blendColor(float red, float green, float blue, float alpha) { + gl2.glBlendColor(red, green, blue, alpha); + } + + @Override + public void alphaFunc(int func, float ref) { + if (gl2x != null) { + gl2x.glAlphaFunc(func, ref); + } else { + throw new RuntimeException(String.format(MISSING_GLFUNC_ERROR, "glAlphaFunc()")); + } + } + + /////////////////////////////////////////////////////////// + + // Whole Framebuffer Operations + + @Override + public void colorMask(boolean r, boolean g, boolean b, boolean a) { + gl.glColorMask(r, g, b, a); + } + + @Override + public void depthMask(boolean mask) { + gl.glDepthMask(mask); + } + + @Override + public void stencilMask(int mask) { + gl.glStencilMask(mask); + } + + @Override + public void stencilMaskSeparate(int face, int mask) { + gl2.glStencilMaskSeparate(face, mask); + } + + @Override + public void clear(int buf) { + gl.glClear(buf); + } + + @Override + public void clearColor(float r, float g, float b, float a) { + gl.glClearColor(r, g, b, a); + } + + @Override + public void clearDepth(float d) { + gl.glClearDepthf(d); + } + + @Override + public void clearStencil(int s) { + gl.glClearStencil(s); + } + + /////////////////////////////////////////////////////////// + + // Framebuffers Objects + + @Override + protected void bindFramebufferImpl(int target, int framebuffer) { + gl.glBindFramebuffer(target, framebuffer); + } + + @Override + public void deleteFramebuffers(int n, IntBuffer framebuffers) { + gl.glDeleteFramebuffers(n, framebuffers); + } + + @Override + public void genFramebuffers(int n, IntBuffer framebuffers) { + gl.glGenFramebuffers(n, framebuffers); + } + + @Override + public void bindRenderbuffer(int target, int renderbuffer) { + gl.glBindRenderbuffer(target, renderbuffer); + } + + @Override + public void deleteRenderbuffers(int n, IntBuffer renderbuffers) { + gl.glDeleteRenderbuffers(n, renderbuffers); + } + + @Override + public void genRenderbuffers(int n, IntBuffer renderbuffers) { + gl.glGenRenderbuffers(n, renderbuffers); + } + + @Override + public void renderbufferStorage(int target, int internalFormat, int width, int height) { + gl.glRenderbufferStorage(target, internalFormat, width, height); + } + + @Override + public void framebufferRenderbuffer(int target, int attachment, int rendbuferfTarget, int renderbuffer) { + gl.glFramebufferRenderbuffer(target, attachment, rendbuferfTarget, renderbuffer); + } + + @Override + public void framebufferTexture2D(int target, int attachment, int texTarget, int texture, int level) { + gl.glFramebufferTexture2D(target, attachment, texTarget, texture, level); + } + + @Override + public int checkFramebufferStatus(int target) { + return gl.glCheckFramebufferStatus(target); + } + + @Override + public boolean isFramebuffer(int framebuffer) { + return gl2.glIsFramebuffer(framebuffer); + } + + @Override + public void getFramebufferAttachmentParameteriv(int target, int attachment, int pname, IntBuffer params) { + gl2.glGetFramebufferAttachmentParameteriv(target, attachment, pname, params); + } + + @Override + public boolean isRenderbuffer(int renderbuffer) { + return gl2.glIsRenderbuffer(renderbuffer); + } + + @Override + public void getRenderbufferParameteriv(int target, int pname, IntBuffer params) { + gl2.glGetRenderbufferParameteriv(target, pname, params); + } + + @Override + public void blitFramebuffer(int srcX0, int srcY0, int srcX1, int srcY1, int dstX0, int dstY0, int dstX1, int dstY1, int mask, int filter) { + if (gl2x != null) { + gl2x.glBlitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter); + } else if (gl3 != null) { + gl3.glBlitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter); + } else { + throw new RuntimeException(String.format(MISSING_GLFUNC_ERROR, "glBlitFramebuffer()")); + } + } + + @Override + public void renderbufferStorageMultisample(int target, int samples, int format, int width, int height) { + if (gl2x != null) { + gl2x.glRenderbufferStorageMultisample(target, samples, format, width, height); + } else if (gl3 != null) { + gl3.glRenderbufferStorageMultisample(target, samples, format, width, height); + } else { + throw new RuntimeException(String.format(MISSING_GLFUNC_ERROR, "glRenderbufferStorageMultisample()")); + } + } + + @Override + public void readBuffer(int buf) { + if (gl2x != null) { + gl2x.glReadBuffer(buf); + } else if (gl3 != null) { + gl3.glReadBuffer(buf); + } else { + throw new RuntimeException(String.format(MISSING_GLFUNC_ERROR, "glReadBuffer()")); + } + } + + @Override + public void drawBuffer(int buf) { + if (gl2x != null) { + gl2x.glDrawBuffer(buf); + } else if (gl3 != null) { + gl3.glDrawBuffer(buf); + } else { + throw new RuntimeException(String.format(MISSING_GLFUNC_ERROR, "glDrawBuffer()")); + } + } +} diff --git a/java/libraries/jogl/src/processing/jogl/PSurfaceNEWT.java b/java/libraries/jogl/src/processing/jogl/PSurfaceNEWT.java new file mode 100644 index 000000000..a38e2b57a --- /dev/null +++ b/java/libraries/jogl/src/processing/jogl/PSurfaceNEWT.java @@ -0,0 +1,713 @@ +package processing.opengl; + +import java.awt.Canvas; +import java.awt.Color; +import java.awt.EventQueue; +import java.awt.Frame; +import java.awt.Rectangle; +//import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; + +import javax.media.nativewindow.NativeSurface; +import javax.media.nativewindow.ScalableSurface; +import javax.media.opengl.GLAnimatorControl; +import javax.media.opengl.GLAutoDrawable; +import javax.media.opengl.GLCapabilities; +import javax.media.opengl.GLEventListener; +import javax.media.opengl.GLException; +import javax.media.opengl.GLProfile; + +import com.jogamp.nativewindow.MutableGraphicsConfiguration; +import com.jogamp.newt.Display; +import com.jogamp.newt.MonitorDevice; +import com.jogamp.newt.NewtFactory; +import com.jogamp.newt.Screen; +import com.jogamp.newt.awt.NewtCanvasAWT; +import com.jogamp.newt.event.InputEvent; +import com.jogamp.newt.event.WindowAdapter; +import com.jogamp.newt.event.WindowEvent; +import com.jogamp.newt.opengl.GLWindow; +import com.jogamp.opengl.util.FPSAnimator; + +import processing.core.PApplet; +import processing.core.PConstants; +import processing.core.PGraphics; +import processing.core.PImage; +import processing.core.PSurface; +import processing.event.KeyEvent; +import processing.event.MouseEvent; + +public class PSurfaceNEWT implements PSurface { + /** Selected GL profile */ + public static GLProfile profile; + + PJOGL pgl; + + GLWindow window; + Frame frame; + FPSAnimator animator; + Rectangle screenRect; + + PApplet sketch; + PGraphics graphics; + + int sketchWidth; + int sketchHeight; + + MonitorDevice displayDevice; + Throwable drawException; + Object waitObject = new Object(); + + public PSurfaceNEWT(PGraphics graphics) { + this.graphics = graphics; + this.pgl = (PJOGL) ((PGraphicsOpenGL)graphics).pgl; + } + + public void initOffscreen() { + // TODO Auto-generated method stub + + } + + public Canvas initCanvas(PApplet sketch) { + this.sketch = sketch; + + sketchWidth = sketch.sketchWidth(); + sketchHeight = sketch.sketchHeight(); + + if (window != null) { + NewtCanvasAWT canvas = new NewtCanvasAWT(window); + canvas.setBounds(0, 0, window.getWidth(), window.getHeight()); +// canvas.setBackground(new Color(pg.backgroundColor, true)); + canvas.setFocusable(true); + + return canvas; + } + + return null; + } + + public Frame initFrame(PApplet sketch, Color backgroundColor, + int deviceIndex, boolean fullScreen, + boolean spanDisplays) { + this.sketch = sketch; + + Display display = NewtFactory.createDisplay(null); + display.addReference(); + Screen screen = NewtFactory.createScreen(display, 0); + screen.addReference(); + + ArrayList monitors = new ArrayList(); + for (int i = 0; i < screen.getMonitorDevices().size(); i++) { + MonitorDevice monitor = screen.getMonitorDevices().get(i); + System.out.println("Monitor " + monitor.getId() + " ************"); + System.out.println(monitor.toString()); + System.out.println(monitor.getViewportInWindowUnits()); + System.out.println(monitor.getViewport()); + + monitors.add(monitor); + } + System.out.println("*******************************"); + + if (deviceIndex >= 0) { // if -1, use the default device + if (deviceIndex < monitors.size()) { + displayDevice = monitors.get(deviceIndex); + } else { + System.err.format("Display %d does not exist, " + + "using the default display instead.", deviceIndex); + for (int i = 0; i < monitors.size(); i++) { + System.err.format("Display %d is %s\n", i, monitors.get(i)); + } + } + } + + if (profile == null) { + if (PJOGL.PROFILE == 2) { + try { + profile = GLProfile.getGL2ES1(); + } catch (GLException ex) { + profile = GLProfile.getMaxFixedFunc(true); + } + } else if (PJOGL.PROFILE == 3) { + try { + profile = GLProfile.getGL2GL3(); + } catch (GLException ex) { + profile = GLProfile.getMaxProgrammable(true); + } + if (!profile.isGL3()) { + PGraphics.showWarning("Requested profile GL3 but is not available, got: " + profile); + } + } else if (PJOGL.PROFILE == 4) { + try { + profile = GLProfile.getGL4ES3(); + } catch (GLException ex) { + profile = GLProfile.getMaxProgrammable(true); + } + if (!profile.isGL4()) { + PGraphics.showWarning("Requested profile GL4 but is not available, got: " + profile); + } + } else throw new RuntimeException(PGL.UNSUPPORTED_GLPROF_ERROR); + } + + // Setting up the desired capabilities; + GLCapabilities caps = new GLCapabilities(profile); + caps.setAlphaBits(PGL.REQUESTED_ALPHA_BITS); + caps.setDepthBits(PGL.REQUESTED_DEPTH_BITS); + caps.setStencilBits(PGL.REQUESTED_STENCIL_BITS); + +// caps.setPBuffer(false); +// caps.setFBO(false); + + pgl.reqNumSamples = graphics.quality; + caps.setSampleBuffers(true); + caps.setNumSamples(pgl.reqNumSamples); + caps.setBackgroundOpaque(true); + caps.setOnscreen(true); + pgl.capabilities = caps; + System.err.println("0. create window"); + window = GLWindow.create(screen, caps); + + sketchWidth = sketch.sketchWidth(); + sketchHeight = sketch.sketchHeight(); + + if (displayDevice == null) { + displayDevice = window.getMainMonitor(); + } + int sketchX = displayDevice.getViewportInWindowUnits().getX(); + int sketchY = displayDevice.getViewportInWindowUnits().getY(); + + int screenWidth = screen.getWidth(); + int screenHeight = screen.getHeight(); + + screenRect = spanDisplays ? new Rectangle(0, 0, screen.getWidth(), screen.getHeight()) : + new Rectangle(0, 0, displayDevice.getViewportInWindowUnits().getWidth(), + displayDevice.getViewportInWindowUnits().getHeight()); + + // Sketch has already requested to be the same as the screen's + // width and height, so let's roll with full screen mode. + if (screenRect.width == sketchWidth && + screenRect.height == sketchHeight) { + fullScreen = true; + } + + if (fullScreen || spanDisplays) { + sketchWidth = screenRect.width; + sketchHeight = screenRect.height; + } + +// window..setBackground(new Color(backgroundColor, true)); + window.setPosition(sketchX, sketchY); + window.setSize(sketchWidth, sketchHeight); + + System.out.println("deviceIndex: " + deviceIndex); + System.out.println(displayDevice); + System.out.println("Screen res " + screenWidth + "x" + screenHeight); + + // This example could be useful: + // com.jogamp.opengl.test.junit.newt.mm.TestScreenMode01cNEWT + if (fullScreen) { + if (spanDisplays) { + window.setFullscreen(monitors); + } else { + window.setFullscreen(true); + } + } + + int[] reqSurfacePixelScale; + if (graphics.is2X()) { + // Retina + reqSurfacePixelScale = new int[] { ScalableSurface.AUTOMAX_PIXELSCALE, + ScalableSurface.AUTOMAX_PIXELSCALE }; + pgl.pixel_scale = 2; + } else { + // Non-retina + reqSurfacePixelScale = new int[] { ScalableSurface.IDENTITY_PIXELSCALE, + ScalableSurface.IDENTITY_PIXELSCALE }; + pgl.pixel_scale = 1; + } + window.setSurfaceScale(reqSurfacePixelScale); + + NEWTMouseListener mouseListener = new NEWTMouseListener(); + window.addMouseListener(mouseListener); + NEWTKeyListener keyListener = new NEWTKeyListener(); + window.addKeyListener(keyListener); + NEWTWindowListener winListener = new NEWTWindowListener(); + window.addWindowListener(winListener); + + DrawListener drawlistener = new DrawListener(); + window.addGLEventListener(drawlistener); + + System.err.println("0. create animator"); + animator = new FPSAnimator(window, 60); + drawException = null; + animator.setUncaughtExceptionHandler(new GLAnimatorControl.UncaughtExceptionHandler() { + @Override + public void uncaughtException(final GLAnimatorControl animator, + final GLAutoDrawable drawable, + final Throwable cause) { + synchronized (waitObject) { +// System.err.println("Caught exception: " + cause.getMessage()); + drawException = cause; + waitObject.notify(); + } + } + }); + + (new Thread(new Runnable() { + public void run() { + synchronized (waitObject) { + try { + if (drawException == null) waitObject.wait(); + } catch (InterruptedException e) { + e.printStackTrace(); + } +// System.err.println("Caught exception: " + drawException.getMessage()); + if (drawException instanceof RuntimeException) { + throw (RuntimeException)drawException.getCause(); + } else { + throw new RuntimeException(drawException.getCause()); + } + } + } + } + )).start(); + + + /* + try { + EventQueue.invokeAndWait(new Runnable() { + public void run() { + while (true) { + try { + if (drawException != null) { + if (drawException instanceof RuntimeException) { + throw (RuntimeException)drawException; + } else { + throw new RuntimeException(drawException); + } + } else { + Thread.sleep(100); + } + } catch (InterruptedException e) { } + } + }}); + } catch (Exception ex) { + } +*/ + + + window.addWindowListener(new WindowAdapter() { + @Override + public void windowDestroyNotify(final WindowEvent e) { + animator.stop(); + PSurfaceNEWT.this.sketch.exit(); + window.destroy(); + } + }); + + +// window.setVisible(true); + try { + EventQueue.invokeAndWait(new Runnable() { + public void run() { + window.setVisible(true); + System.err.println("1. set visible"); + }}); + } catch (Exception ex) { + // error setting the window visible, should quit... + } + + frame = new DummyFrame(); + return frame; + } + + class DummyFrame extends Frame { + + public DummyFrame() { + super(); + } + + @Override + public void setResizable(boolean resizable) { +// super.setResizable(resizable); + } + + @Override + public void setVisible(boolean visible) { + window.setVisible(visible); + } + + @Override + public void setTitle(String title) { + window.setTitle(title); + } + } + + + public void setTitle(String title) { + window.setTitle(title); + } + + public void setVisible(boolean visible) { + window.setVisible(visible); + } + + public void setResizable(boolean resizable) { + // TODO Auto-generated method stub + + } + + public void placeWindow(int[] location) { + // TODO Auto-generated method stub + + } + + public void placeWindow(int[] location, int[] editorLocation) { + // TODO Auto-generated method stub + + } + + public void placePresent(Color stopColor) { + // TODO Auto-generated method stub + + } + + public void setupExternalMessages() { + // TODO Auto-generated method stub + + } + + public void startThread() { + if (animator != null) { + System.err.println("2. start animator"); + animator.start(); + animator.getThread().setName("Processing-GL-draw"); + } + } + + public void pauseThread() { + if (animator != null) { + animator.pause(); + } + } + + public void resumeThread() { + if (animator != null) { + animator.resume(); + } + } + + public boolean stopThread() { + if (animator != null) { + return animator.stop(); + } else { + return false; + } + } + + public boolean isStopped() { + if (animator != null) { + return !animator.isAnimating(); + } else { + return true; + } + } + + public void setSize(int width, int height) { + if (frame != null) { + System.err.println("3. set size"); + sketchWidth = sketch.width = width; + sketchHeight = sketch.height = height; + graphics.setSize(width, height); + } + } + + public void setSmooth(int level) { + pgl.reqNumSamples = level; + GLCapabilities caps = new GLCapabilities(profile); + caps.setAlphaBits(PGL.REQUESTED_ALPHA_BITS); + caps.setDepthBits(PGL.REQUESTED_DEPTH_BITS); + caps.setStencilBits(PGL.REQUESTED_STENCIL_BITS); + caps.setSampleBuffers(true); + caps.setNumSamples(pgl.reqNumSamples); + caps.setBackgroundOpaque(true); + caps.setOnscreen(true); + NativeSurface target = window.getNativeSurface(); + MutableGraphicsConfiguration config = (MutableGraphicsConfiguration) target.getGraphicsConfiguration(); + config.setChosenCapabilities(caps); + } + + public void setFrameRate(float fps) { + if (animator != null) { + animator.stop(); + animator.setFPS((int)fps); + pgl.setFps(fps); + animator.start(); + } + } + + public void requestFocus() { + window.requestFocus(); + + } + + public void blit() { + // TODO Auto-generated method stub + } + + class DrawListener implements GLEventListener { + public void display(GLAutoDrawable drawable) { + pgl.getGL(drawable); +// System.out.println(" - " + sketch.frameCount); + sketch.handleDraw(); + + if (sketch.frameCount == 1) { + requestFocus(); + } + } + public void dispose(GLAutoDrawable drawable) { + pgl.getGL(drawable); + sketch.dispose(); + if (sketch.exitCalled()) { + sketch.exitActual(); + } + } + public void init(GLAutoDrawable drawable) { + pgl.init(drawable); + pgl.getGL(drawable); + sketch.start(); + + int c = graphics.backgroundColor; + pgl.clearColor(((c >> 16) & 0xff) / 255f, + ((c >> 8) & 0xff) / 255f, + ((c >> 0) & 0xff) / 255f, + ((c >> 24) & 0xff) / 255f); + pgl.clear(PGL.COLOR_BUFFER_BIT); + } + + public void reshape(GLAutoDrawable drawable, int x, int y, int w, int h) { + pgl.getGL(drawable); + setSize(w, h); + } + } + + protected class NEWTWindowListener implements com.jogamp.newt.event.WindowListener { + public NEWTWindowListener() { + super(); + } + @Override + public void windowGainedFocus(com.jogamp.newt.event.WindowEvent arg0) { +// pg.parent.focusGained(null); + } + + @Override + public void windowLostFocus(com.jogamp.newt.event.WindowEvent arg0) { +// pg.parent.focusLost(null); + } + + @Override + public void windowDestroyNotify(com.jogamp.newt.event.WindowEvent arg0) { + } + + @Override + public void windowDestroyed(com.jogamp.newt.event.WindowEvent arg0) { + } + + @Override + public void windowMoved(com.jogamp.newt.event.WindowEvent arg0) { + } + + @Override + public void windowRepaint(com.jogamp.newt.event.WindowUpdateEvent arg0) { + } + + @Override + public void windowResized(com.jogamp.newt.event.WindowEvent arg0) { } + } + + // NEWT mouse listener + protected class NEWTMouseListener extends com.jogamp.newt.event.MouseAdapter { + public NEWTMouseListener() { + super(); + } + @Override + public void mousePressed(com.jogamp.newt.event.MouseEvent e) { + nativeMouseEvent(e, MouseEvent.PRESS); + } + @Override + public void mouseReleased(com.jogamp.newt.event.MouseEvent e) { + nativeMouseEvent(e, MouseEvent.RELEASE); + } + @Override + public void mouseClicked(com.jogamp.newt.event.MouseEvent e) { + nativeMouseEvent(e, MouseEvent.CLICK); + } + @Override + public void mouseDragged(com.jogamp.newt.event.MouseEvent e) { + nativeMouseEvent(e, MouseEvent.DRAG); + } + @Override + public void mouseMoved(com.jogamp.newt.event.MouseEvent e) { + nativeMouseEvent(e, MouseEvent.MOVE); + } + @Override + public void mouseWheelMoved(com.jogamp.newt.event.MouseEvent e) { + nativeMouseEvent(e, MouseEvent.WHEEL); + } + @Override + public void mouseEntered(com.jogamp.newt.event.MouseEvent e) { + nativeMouseEvent(e, MouseEvent.ENTER); + } + @Override + public void mouseExited(com.jogamp.newt.event.MouseEvent e) { + nativeMouseEvent(e, MouseEvent.EXIT); + } + } + + // NEWT key listener + protected class NEWTKeyListener extends com.jogamp.newt.event.KeyAdapter { + public NEWTKeyListener() { + super(); + } + @Override + public void keyPressed(com.jogamp.newt.event.KeyEvent e) { + nativeKeyEvent(e, KeyEvent.PRESS); + } + @Override + public void keyReleased(com.jogamp.newt.event.KeyEvent e) { + nativeKeyEvent(e, KeyEvent.RELEASE); + } + public void keyTyped(com.jogamp.newt.event.KeyEvent e) { + nativeKeyEvent(e, KeyEvent.TYPE); + } + } + + protected void nativeMouseEvent(com.jogamp.newt.event.MouseEvent nativeEvent, + int peAction) { + int modifiers = nativeEvent.getModifiers(); + int peModifiers = modifiers & + (InputEvent.SHIFT_MASK | + InputEvent.CTRL_MASK | + InputEvent.META_MASK | + InputEvent.ALT_MASK); + + int peButton = 0; + if ((modifiers & InputEvent.BUTTON1_MASK) != 0) { + peButton = PConstants.LEFT; + } else if ((modifiers & InputEvent.BUTTON2_MASK) != 0) { + peButton = PConstants.CENTER; + } else if ((modifiers & InputEvent.BUTTON3_MASK) != 0) { + peButton = PConstants.RIGHT; + } + + if (PApplet.platform == PConstants.MACOSX) { + //if (nativeEvent.isPopupTrigger()) { + if ((modifiers & InputEvent.CTRL_MASK) != 0) { + peButton = PConstants.RIGHT; + } + } + + int peCount = 0; + if (peAction == MouseEvent.WHEEL) { + peCount = nativeEvent.isShiftDown() ? (int)nativeEvent.getRotation()[0] : + (int)nativeEvent.getRotation()[1]; + } else { + peCount = nativeEvent.getClickCount(); + } + + MouseEvent me = new MouseEvent(nativeEvent, nativeEvent.getWhen(), + peAction, peModifiers, + nativeEvent.getX(), nativeEvent.getY(), + peButton, + peCount); + + sketch.postEvent(me); + } + + protected void nativeKeyEvent(com.jogamp.newt.event.KeyEvent nativeEvent, + int peAction) { + int peModifiers = nativeEvent.getModifiers() & + (InputEvent.SHIFT_MASK | + InputEvent.CTRL_MASK | + InputEvent.META_MASK | + InputEvent.ALT_MASK); + + short code = nativeEvent.getKeyCode(); + char keyChar; + int keyCode; + if (isPCodedKey(code)) { + keyCode = mapToPConst(code); + keyChar = PConstants.CODED; + } else { + keyCode = code; + keyChar = nativeEvent.getKeyChar(); + } + + // From http://jogamp.org/deployment/v2.1.0/javadoc/jogl/javadoc/com/jogamp/newt/event/KeyEvent.html + // public final short getKeySymbol() + // Returns the virtual key symbol reflecting the current keyboard layout. + // public final short getKeyCode() + // Returns the virtual key code using a fixed mapping to the US keyboard layout. + // In contrast to key symbol, key code uses a fixed US keyboard layout and therefore is keyboard layout independent. + // E.g. virtual key code VK_Y denotes the same physical key regardless whether keyboard layout QWERTY or QWERTZ is active. The key symbol of the former is VK_Y, where the latter produces VK_Y. + KeyEvent ke = new KeyEvent(nativeEvent, nativeEvent.getWhen(), + peAction, peModifiers, + keyChar, + keyCode); +// nativeEvent.getKeySymbol()); + + sketch.postEvent(ke); + } + + // Why do we need this mapping? + // Relevant discussion and links here: + // http://forum.jogamp.org/Newt-wrong-keycode-for-key-td4033690.html#a4033697 + // (I don't think this is a complete solution). + private static int mapToPConst(short code) { + if (code == com.jogamp.newt.event.KeyEvent.VK_UP) { + return PConstants.UP; + } else if (code == com.jogamp.newt.event.KeyEvent.VK_DOWN) { + return PConstants.DOWN; + } else if (code == com.jogamp.newt.event.KeyEvent.VK_LEFT) { + return PConstants.LEFT; + } else if (code == com.jogamp.newt.event.KeyEvent.VK_RIGHT) { + return PConstants.RIGHT; + } else if (code == com.jogamp.newt.event.KeyEvent.VK_ALT) { + return PConstants.ALT; + } else if (code == com.jogamp.newt.event.KeyEvent.VK_CONTROL) { + return PConstants.CONTROL; + } else if (code == com.jogamp.newt.event.KeyEvent.VK_SHIFT) { + return PConstants.SHIFT; + } + return code; + } + + private static boolean isPCodedKey(short code) { + return code == com.jogamp.newt.event.KeyEvent.VK_UP || + code == com.jogamp.newt.event.KeyEvent.VK_DOWN || + code == com.jogamp.newt.event.KeyEvent.VK_LEFT || + code == com.jogamp.newt.event.KeyEvent.VK_RIGHT || + code == com.jogamp.newt.event.KeyEvent.VK_ALT || + code == com.jogamp.newt.event.KeyEvent.VK_CONTROL || + code == com.jogamp.newt.event.KeyEvent.VK_SHIFT; + } + + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + public void setCursor(int kind) { + // TODO Auto-generated method stub + + } + + public void setCursor(PImage image, int hotspotX, int hotspotY) { + // TODO Auto-generated method stub + + } + + public void showCursor() { + window.setPointerVisible(true); + } + + public void hideCursor() { + window.setPointerVisible(false); + } +}