Browse code

Surface fillTriangles methods that render slices.

Courtesy of ruslansennov, with various revampings by yours truly.

These are a little complicated. You can pass in a slice of a vertices array and
you can pass in a slice of an indices array. However, the indices array is
supposed to provide indices into the xys array, so the question is: do they
provide indices into the slice, or indices into the whole array?

Because I can imagine use cases for either choice, this is left up to the
caller as well, so you have to specify an indicesBase which would be zero if
your indices are relative to the slice, or xysOffset/2 if your indices are
based on the unsliced array.

Closes #31.

Michael Bayne authored on 08/05/2014 17:36:55
Showing 6 changed files
... ...
@@ -194,17 +194,70 @@ public interface Surface {
194 194
   Surface fillTriangles(float[] xys, int[] indices);
195 195
 
196 196
   /**
197
-   * Fills the supplied batch of triangles with the current fill pattern. Note: this method only
198
-   * honors the texture coordinates on OpenGL-based backends (Anrdoid, iOS, HTML-WebGL, etc.). On
199
-   * non-OpenGL-based backends (HTML-Canvas, HTML-Flash) it behaves like a call to {@link
200
-   * #fillTriangles(float[],int[])}.
197
+   * Fills the supplied batch of triangles with the current fill color or pattern.
198
+   *
199
+   * <p>Note: this method is only performant on OpenGL-based backends (Android, iOS, HTML-WebGL,
200
+   * etc.). On non-OpenGL-based backends (HTML-Canvas, HTML-Flash) it converts the triangles to a
201
+   * path on every rendering call.</p>
201 202
    *
202 203
    * @param xys the xy coordinates of the triangles, as an array: {@code [x1, y1, x2, y2, ...]}.
204
+   * @param xysOffset the offset of the coordinates array, must not be negative and no greater than
205
+   * {@code xys.length}. Note: this is an absolute offset; since {@code xys} contains pairs of
206
+   * values, this will be some multiple of two.
207
+   * @param xysLen the number of coordinates to read, must be no less than zero and no greater than
208
+   * {@code xys.length - xysOffset}. Note: this is an absolute length; since {@code xys} contains
209
+   * pairs of values, this will be some multiple of two.
210
+   * @param indices the index of each vertex of each triangle in the {@code xys} array. Because
211
+   * this method renders a slice of {@code xys}, one must also specify {@code indexBase} which
212
+   * tells us how to interpret indices. The index into {@code xys} will be computed as: {@code
213
+   * 2*(indices[ii] - indexBase)}, so if your indices reference vertices relative to the whole
214
+   * array you should pass {@code xysOffset/2} for {@code indexBase}, but if your indices reference
215
+   * vertices relative to <em>the slice</em> then you should pass zero.
216
+   * @param indicesOffset the offset of the indices array, must not be negative and no greater than
217
+   * {@code indices.length}.
218
+   * @param indicesLen the number of indices to read, must be no less than zero and no greater than
219
+   * {@code indices.length - indicesOffset}.
220
+   * @param indexBase the basis for interpreting {@code indices}. See the docs for {@code indices}
221
+   * for details.
222
+   */
223
+  Surface fillTriangles(float[] xys, int xysOffset, int xysLen,
224
+                        int[] indices, int indicesOffset, int indicesLen, int indexBase);
225
+
226
+  /**
227
+   * Fills the supplied batch of triangles with the current fill pattern.
228
+   *
229
+   * <p>Note: this method only honors the texture coordinates on OpenGL-based backends (Anrdoid,
230
+   * iOS, HTML-WebGL, etc.). On non-OpenGL-based backends (HTML-Canvas, HTML-Flash) it behaves like
231
+   * a call to {@link #fillTriangles(float[],int[])}.</p>
232
+   *
233
+   * @param xys see {@link #fillTriangles(float[],int[])}.
203 234
    * @param sxys the texture coordinates for each vertex of the triangles, as an array:
204 235
    * {@code [sx1, sy1, sx2, sy2, ...]}. This must be the same length as {@code xys}.
205
-   * @param indices the index of each vertex of each triangle in the {@code xys} array.
236
+   * @param indices see {@link #fillTriangles(float[],int[])}.
206 237
    *
207 238
    * @throws IllegalStateException if no fill pattern is currently set.
208 239
    */
209 240
   Surface fillTriangles(float[] xys, float[] sxys, int[] indices);
241
+
242
+  /**
243
+   * Fills the supplied batch of triangles with the current fill pattern.
244
+   *
245
+   * <p>Note: this method only honors the texture coordinates on OpenGL-based backends (Anrdoid,
246
+   * iOS, HTML-WebGL, etc.). On non-OpenGL-based backends (HTML-Canvas, HTML-Flash) it behaves like
247
+   * a call to {@link #fillTriangles(float[],int[])}.</p>
248
+   *
249
+   * @param xys see {@link #fillTriangles(float[],int,int,int[],int,int,int)}.
250
+   * @param sxys the texture coordinates for each vertex of the triangles, as an array.
251
+   * {@code [sx1, sy1, sx2, sy2, ...]}. This must be the same length as {@code xys}.
252
+   * @param xysOffset see {@link #fillTriangles(float[],int,int,int[],int,int,int)}.
253
+   * @param xysLen see {@link #fillTriangles(float[],int,int,int[],int,int,int)}.
254
+   * @param indices see {@link #fillTriangles(float[],int,int,int[],int,int,int)}.
255
+   * @param indicesOffset see {@link #fillTriangles(float[],int,int,int[],int,int,int)}.
256
+   * @param indicesLen see {@link #fillTriangles(float[],int,int,int[],int,int,int)}.
257
+   * @param indexBase see {@link #fillTriangles(float[],int,int,int[],int,int,int)}.
258
+   *
259
+   * @throws IllegalStateException if no fill pattern is currently set.
260
+   */
261
+  Surface fillTriangles(float[] xys, float[] sxys, int xysOffset, int xysLen,
262
+                        int[] indices, int indicesOffset, int indicesLen, int indexBase);
210 263
 }
... ...
@@ -90,9 +90,17 @@ public class CanvasSurface implements Surface {
90 90
 
91 91
   @Override
92 92
   public Surface fillTriangles(float[] xys, int[] indices) {
93
+    return fillTriangles(xys, 0, xys.length, indices, 0, indices.length, 0);
94
+  }
95
+
96
+  @Override
97
+  public Surface fillTriangles(float[] xys, int xysOffset, int xysLen,
98
+                               int[] indices, int indicesOffset, int indicesLen, int indexBase) {
93 99
     Path path = canvas.createPath();
94
-    for (int ii = 0; ii < indices.length; ii += 3) {
95
-      int a = 2*indices[ii], b = 2*indices[ii+1], c = 2*indices[ii+2];
100
+    for (int ii = indicesOffset, ll = ii+indicesLen; ii < ll; ii += 3) {
101
+      int a = 2*(indices[ii  ] - indexBase) + xysOffset;
102
+      int b = 2*(indices[ii+1] - indexBase) + xysOffset;
103
+      int c = 2*(indices[ii+2] - indexBase) + xysOffset;
96 104
       path.moveTo(xys[a], xys[a+1]);
97 105
       path.lineTo(xys[b], xys[b+1]);
98 106
       path.lineTo(xys[c], xys[c+1]);
... ...
@@ -110,6 +118,14 @@ public class CanvasSurface implements Surface {
110 118
   }
111 119
 
112 120
   @Override
121
+  public Surface fillTriangles(float[] xys, float[] sxys, int xysOffset, int xysLen,
122
+                               int[] indices, int indicesOffset, int indicesLen, int indexBase) {
123
+    // canvas-based surfaces can't handle texture coordinates, so ignore them; the caller has been
124
+    // warned of this sub-optimal fallback behavior in this method's javadocs
125
+    return fillTriangles(xys, xysOffset, xysLen, indices, indicesOffset, indicesLen, indexBase);
126
+  }
127
+
128
+  @Override
113 129
   public float height() {
114 130
     return canvas.height();
115 131
   }
... ...
@@ -150,6 +150,12 @@ abstract class AbstractSurfaceGL implements Surface {
150 150
 
151 151
   @Override
152 152
   public Surface fillTriangles(float[] xys, int[] indices) {
153
+    return fillTriangles(xys, 0, xys.length, indices, 0, indices.length, 0);
154
+  }
155
+
156
+  @Override
157
+  public Surface fillTriangles(float[] xys, int xysOffset, int xysLen,
158
+                               int[] indices, int indicesOffset, int indicesLen, int indexBase) {
153 159
     bindFramebuffer();
154 160
 
155 161
     GLShader shader = ctx.trisShader(this.shader);
... ...
@@ -157,18 +163,27 @@ abstract class AbstractSurfaceGL implements Surface {
157 163
       int tex = fillPattern.ensureTexture();
158 164
       if (tex > 0) {
159 165
         shader.prepareTexture(tex, tint);
160
-        shader.addTriangles(topTransform(), xys, fillPattern.width(), fillPattern.height(), indices);
166
+        shader.addTriangles(topTransform(), xys, xysOffset, xysLen,
167
+                            fillPattern.width(), fillPattern.height(),
168
+                            indices, indicesOffset, indicesLen, indexBase);
161 169
       }
162 170
     } else {
163 171
       int tex = ctx.fillImage().ensureTexture();
164 172
       shader.prepareTexture(tex, Tint.combine(fillColor, tint));
165
-      shader.addTriangles(topTransform(), xys, 1, 1, indices);
173
+      shader.addTriangles(topTransform(), xys, xysOffset, xysLen, 1, 1,
174
+                          indices, indicesOffset, indicesLen, indexBase);
166 175
     }
167 176
     return this;
168 177
   }
169 178
 
170 179
   @Override
171 180
   public Surface fillTriangles(float[] xys, float[] sxys, int[] indices) {
181
+    return fillTriangles(xys, sxys, 0, xys.length, indices, 0, indices.length, 0);
182
+  }
183
+
184
+  @Override
185
+  public Surface fillTriangles(float[] xys, float[] sxys, int xysOffset, int xysLen,
186
+                               int[] indices, int indicesOffset, int indicesLen, int indexBase) {
172 187
     bindFramebuffer();
173 188
 
174 189
     if (fillPattern == null)
... ...
@@ -176,7 +191,8 @@ abstract class AbstractSurfaceGL implements Surface {
176 191
     int tex = fillPattern.ensureTexture();
177 192
     if (tex > 0) {
178 193
       GLShader shader = ctx.trisShader(this.shader).prepareTexture(tex, tint);
179
-      shader.addTriangles(topTransform(), xys, sxys, indices);
194
+      shader.addTriangles(topTransform(), xys, sxys, xysOffset, xysLen,
195
+                          indices, indicesOffset, indicesLen, indexBase);
180 196
     }
181 197
     return this;
182 198
   }
... ...
@@ -172,30 +172,51 @@ public abstract class GLShader {
172 172
    * Adds a collection of triangles to the current render operation.
173 173
    *
174 174
    * @param xys a list of x/y coordinates as: {@code [x1, y1, x2, y2, ...]}.
175
+   * @param xysOffset the offset of the coordinates array, must not be negative and no greater than
176
+   * {@code xys.length}. Note: this is an absolute offset; since {@code xys} contains pairs of
177
+   * values, this will be some multiple of two.
178
+   * @param xysLen the number of coordinates to read, must be no less than zero and no greater than
179
+   * {@code xys.length - xysOffset}. Note: this is an absolute length; since {@code xys} contains
180
+   * pairs of values, this will be some multiple of two.
175 181
    * @param tw the width of the texture for which we will auto-generate texture coordinates.
176 182
    * @param th the height of the texture for which we will auto-generate texture coordinates.
177
-   * @param indices the index of the triangle vertices in the supplied {@code xys} array. This must
178
-   * be in proper winding order for OpenGL rendering.
183
+   * @param indices the index of the triangle vertices in the {@code xys} array. Because this
184
+   * method renders a slice of {@code xys}, one must also specify {@code indexBase} which tells us
185
+   * how to interpret indices. The index into {@code xys} will be computed as:
186
+   * {@code 2*(indices[ii] - indexBase)}, so if your indices reference vertices relative to the
187
+   * whole array you should pass {@code xysOffset/2} for {@code indexBase}, but if your indices
188
+   * reference vertices relative to <em>the slice</em> then you should pass zero.
189
+   * @param indicesOffset the offset of the indices array, must not be negative and no greater than
190
+   * {@code indices.length}.
191
+   * @param indicesLen the number of indices to read, must be no less than zero and no greater than
192
+   * {@code indices.length - indicesOffset}.
193
+   * @param indexBase the basis for interpreting {@code indices}. See the docs for {@code indices}
194
+   * for details.
179 195
    */
180
-  public void addTriangles(InternalTransform local, float[] xys, float tw, float th, int[] indices) {
181
-    texCore.addTriangles(local.m00(), local.m01(), local.m10(), local.m11(), local.tx(), local.ty(),
182
-                         xys, tw, th, indices);
183
-    if (GLContext.STATS_ENABLED) ctx.stats.trisRendered += indices.length/3;
196
+  public void addTriangles(InternalTransform local, float[] xys, int xysOffset, int xysLen,
197
+                           float tw, float th,
198
+                           int[] indices, int indicesOffset, int indicesLen, int indexBase) {
199
+    texCore.addTriangles(
200
+      local.m00(), local.m01(), local.m10(), local.m11(), local.tx(), local.ty(),
201
+      xys, xysOffset, xysLen, tw, th, indices, indicesOffset, indicesLen, indexBase);
202
+    if (GLContext.STATS_ENABLED) ctx.stats.trisRendered += indicesLen/3;
184 203
   }
185 204
 
186 205
   /**
187
-   * Adds a collection of triangles to the current render operation.
206
+   * Adds a collection of triangles to the current render operation. See
207
+   * {@link #addTriangles(InternalTransform,float[],int,int,float,float,int[],int,int,int)} for
208
+   * parameter documentation.
188 209
    *
189
-   * @param xys a list of x/y coordinates as: {@code [x1, y1, x2, y2, ...]}.
190 210
    * @param sxys a list of sx/sy texture coordinates as: {@code [sx1, sy1, sx2, sy2, ...]}. This
191 211
    * must be of the same length as {@code xys}.
192
-   * @param indices the index of the triangle vertices in the supplied {@code xys} array. This must
193
-   * be in proper winding order for OpenGL rendering.
194 212
    */
195
-  public void addTriangles(InternalTransform local, float[] xys, float[] sxys, int[] indices) {
196
-    texCore.addTriangles(local.m00(), local.m01(), local.m10(), local.m11(), local.tx(), local.ty(),
197
-                         xys, sxys, indices);
198
-    if (GLContext.STATS_ENABLED) ctx.stats.trisRendered += indices.length/3;
213
+  public void addTriangles(InternalTransform local,
214
+                           float[] xys, float[] sxys, int xysOffset, int xysLen,
215
+                           int[] indices, int indicesOffset, int indicesLen, int indexBase) {
216
+    texCore.addTriangles(
217
+      local.m00(), local.m01(), local.m10(), local.m11(), local.tx(), local.ty(),
218
+      xys, sxys, xysOffset, xysLen, indices, indicesOffset, indicesLen, indexBase);
219
+    if (GLContext.STATS_ENABLED) ctx.stats.trisRendered += indicesLen/3;
199 220
   }
200 221
 
201 222
   /**
... ...
@@ -351,13 +372,15 @@ public abstract class GLShader {
351 372
 
352 373
     /** See {@link GLShader#addTriangles}. */
353 374
     public void addTriangles(float m00, float m01, float m10, float m11, float tx, float ty,
354
-                             float[] xys, float tw, float th, int[] indices) {
375
+                             float[] xys, int xysOffset, int xysLen, float tw, float th,
376
+                             int[] indices, int indicesOffset, int indicesLen, int indexBase) {
355 377
       throw new UnsupportedOperationException("Triangles not supported by this shader");
356 378
     }
357 379
 
358 380
     /** See {@link GLShader#addTriangles}. */
359 381
     public void addTriangles(float m00, float m01, float m10, float m11, float tx, float ty,
360
-                             float[] xys, float[] sxys, int[] indices) {
382
+                             float[] xys, float[] sxys, int xysOffset, int xysLen,
383
+                             int[] indices, int indicesOffset, int indicesLen, int indexBase) {
361 384
       throw new UnsupportedOperationException("Triangles not supported by this shader");
362 385
     }
363 386
 
... ...
@@ -238,12 +238,13 @@ public class IndexedTrisShader extends GLShader {
238 238
       offset = addVert(vertData, offset, stableAttrs, x4, y4, sx4, sy4);
239 239
       vertices.skip(offset - vertices.position());
240 240
 
241
-      addElems(vertIdx, QUAD_INDICES);
241
+      addElems(vertIdx, QUAD_INDICES, 0, QUAD_INDICES.length, 0);
242 242
     }
243 243
 
244 244
     @Override
245 245
     public void addTriangles(float m00, float m01, float m10, float m11, float tx, float ty,
246
-                             float[] xys, float tw, float th, int[] indices) {
246
+                             float[] xys, int xysOffset, int xysLen, float tw, float th,
247
+                             int[] indices, int indicesOffset, int indicesLen, int indexBase) {
247 248
       stableAttrs[0] = m00;
248 249
       stableAttrs[1] = m01;
249 250
       stableAttrs[2] = m10;
... ...
@@ -252,21 +253,22 @@ public class IndexedTrisShader extends GLShader {
252 253
       stableAttrs[5] = ty;
253 254
       addExtraStableAttrs(stableAttrs, 6);
254 255
 
255
-      int vertIdx = beginPrimitive(xys.length/2, indices.length);
256
+      int vertIdx = beginPrimitive(xysLen/2, indicesLen);
256 257
       int offset = vertices.position();
257 258
       float[] vertData = vertices.array();
258
-      for (int ii = 0, ll = xys.length; ii < ll; ii += 2) {
259
+      for (int ii = xysOffset, ll = ii+xysLen; ii < ll; ii += 2) {
259 260
         float x = xys[ii], y = xys[ii+1];
260 261
         offset = addVert(vertData, offset, stableAttrs, x, y, x/tw, y/th);
261 262
       }
262 263
       vertices.skip(offset - vertices.position());
263 264
 
264
-      addElems(vertIdx, indices);
265
+      addElems(vertIdx, indices, indicesOffset, indicesLen, indexBase);
265 266
     }
266 267
 
267 268
     @Override
268 269
     public void addTriangles(float m00, float m01, float m10, float m11, float tx, float ty,
269
-                             float[] xys, float[] sxys, int[] indices) {
270
+                             float[] xys, float[] sxys, int xysOffset, int xysLen,
271
+                             int[] indices, int indicesOffset, int indicesLen, int indexBase) {
270 272
       stableAttrs[0] = m00;
271 273
       stableAttrs[1] = m01;
272 274
       stableAttrs[2] = m10;
... ...
@@ -275,15 +277,15 @@ public class IndexedTrisShader extends GLShader {
275 277
       stableAttrs[5] = ty;
276 278
       addExtraStableAttrs(stableAttrs, 6);
277 279
 
278
-      int vertIdx = beginPrimitive(xys.length/2, indices.length);
280
+      int vertIdx = beginPrimitive(xysLen/2, indicesLen);
279 281
       int offset = vertices.position();
280 282
       float[] vertData = vertices.array();
281
-      for (int ii = 0, ll = xys.length; ii < ll; ii += 2) {
283
+      for (int ii = xysOffset, ll = ii+xysLen; ii < ll; ii += 2) {
282 284
         offset = addVert(vertData, offset, stableAttrs, xys[ii], xys[ii+1], sxys[ii], sxys[ii+1]);
283 285
       }
284 286
       vertices.skip(offset - vertices.position());
285 287
 
286
-      addElems(vertIdx, indices);
288
+      addElems(vertIdx, indices, indicesOffset, indicesLen, indexBase);
287 289
     }
288 290
 
289 291
     @Override
... ...
@@ -327,11 +329,12 @@ public class IndexedTrisShader extends GLShader {
327 329
       return vertIdx;
328 330
     }
329 331
 
330
-    protected final void addElems(int vertIdx, int[] indices) {
332
+    protected final void addElems(int vertIdx, int[] indices, int indicesOffset, int indicesLen,
333
+                                  int indexBase) {
331 334
       short[] data = elements.array();
332 335
       int offset = elements.position();
333
-      for (int ii = 0, ll = indices.length; ii < ll; ii++) {
334
-        data[offset++] = (short)(vertIdx+indices[ii]);
336
+      for (int ii = indicesOffset, ll = ii+indicesLen; ii < ll; ii++) {
337
+        data[offset++] = (short)(vertIdx+indices[ii]-indexBase);
335 338
       }
336 339
       elements.skip(offset - elements.position());
337 340
     }
... ...
@@ -64,7 +64,8 @@ public class SurfaceTest extends Test {
64 64
   protected void addTests (final Image orange, Image tile) {
65 65
     final Pattern pattern = tile.toPattern();
66 66
 
67
-    int samples = 128; // big enough to force a buffer size increase
67
+    // make samples big enough to force a buffer size increase
68
+    final int samples = 128, hsamples = samples/2;
68 69
     final float[] verts = new float[(samples+1)*4];
69 70
     final int[] indices = new int[samples*6];
70 71
     tessellateCurve(0, 40*(float)Math.PI, verts, indices, new F() {
... ...
@@ -110,8 +111,13 @@ public class SurfaceTest extends Test {
110 111
         surf.setFillPattern(pattern).fillRect(10, 0, 100, 100);
111 112
         // use same fill pattern for the triangles
112 113
         surf.translate(0, 160);
113
-        surf.fillTriangles(verts, indices);
114
+        // render a sliding window of half of our triangles to test the slice rendering
115
+        surf.fillTriangles(verts, offset*4, (hsamples+1)*4, indices, offset*6, hsamples*6, offset*2);
116
+        offset += doff;
117
+        if (offset == 0) doff = 1;
118
+        else if (offset == hsamples) doff = -1;
114 119
       }
120
+      private int offset = 0, doff = 1;
115 121
     }, 120, 210, "ImmediateLayer patterned fillRect, fillTriangles");
116 122
 
117 123
     SurfaceImage patted = graphics().createSurface(100, 100);
... ...
@@ -221,7 +227,7 @@ public class SurfaceTest extends Test {
221 227
   void tessellateCurve (float minx, float maxx, float[] verts, int[] indices, F f) {
222 228
     int slices = (verts.length-1)/4, vv = 0;
223 229
     float dx = (maxx-minx)/slices;
224
-    for (float x = minx; x < maxx; x += dx) {
230
+    for (float x = minx; vv < verts.length; x += dx) {
225 231
       verts[vv++] = x;
226 232
       verts[vv++] = 0;
227 233
       verts[vv++] = x;