1 /*
   2  * Copyright (c) 2013, 2014, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.prism.impl;
  27 
  28 import com.sun.javafx.geom.Quat4f;
  29 import com.sun.javafx.geom.Vec2f;
  30 import com.sun.javafx.geom.Vec3f;
  31 import com.sun.prism.Mesh;
  32 import java.util.Arrays;
  33 import java.util.HashMap;
  34 import javafx.scene.shape.VertexFormat;
  35 import sun.util.logging.PlatformLogger;
  36 
  37 /**
  38  * TODO: 3D - Need documentation
  39  */
  40 public abstract class BaseMesh extends BaseGraphicsResource implements Mesh {
  41 
  42     private int nVerts;
  43     private int nTVerts;
  44     private int nFaces;
  45     private float[] pos;
  46     private float[] uv;
  47     private int[] faces;
  48     private int[] smoothing;
  49     private boolean allSameSmoothing;
  50     private boolean allHardEdges;
  51 
  52     protected static final int POINT_SIZE = 3;
  53     protected static final int NORMAL_SIZE = 3;
  54     protected static final int TEXCOORD_SIZE = 2;
  55 
  56     protected static final int POINT_SIZE_VB = 3;
  57     protected static final int TEXCOORD_SIZE_VB = 2;
  58     protected static final int NORMAL_SIZE_VB = 4;
  59     //point (3 floats), texcoord (2 floats) and normal (as in 4 floats)
  60     protected static final int VERTEX_SIZE_VB = 9; 
  61 
  62     // Data members container for a single face
  63     //    Vec3i pVerts;
  64     //    Vec3i tVerts;
  65     //    int  smGroup;
  66     public static enum FaceMembers {
  67         POINT0, TEXCOORD0, POINT1, TEXCOORD1, POINT2, TEXCOORD2, SMOOTHING_GROUP
  68     };
  69     public static final int FACE_MEMBERS_SIZE = 7;
  70 
  71     protected BaseMesh(Disposer.Record disposerRecord) {
  72         super(disposerRecord);
  73     }
  74 
  75     public abstract boolean buildNativeGeometry(float[] vertexBuffer, 
  76             int vertexBufferLength, int[] indexBufferInt, int indexBufferLength);
  77 
  78     public abstract boolean buildNativeGeometry(float[] vertexBuffer,
  79             int vertexBufferLength, short[] indexBufferShort, int indexBufferLength);
  80 
  81     private boolean updateSkipMeshNormalGeometry(int[] posFromAndLengthIndices, int[] uvFromAndLengthIndices) {
  82 
  83         // Find out the list of modified tex coords.
  84         int startTexCoord = uvFromAndLengthIndices[0] / TEXCOORD_SIZE;
  85         int numTexCoords = (uvFromAndLengthIndices[1] / TEXCOORD_SIZE);
  86         if ((uvFromAndLengthIndices[1] % TEXCOORD_SIZE) > 0) {
  87             numTexCoords++;
  88         }
  89 
  90         if (numTexCoords > 0) {
  91             for (int i = 0; i < numTexCoords; i++) {
  92                 int texCoordOffset = (startTexCoord + i) * TEXCOORD_SIZE;
  93                 MeshGeomComp2VB mt2vb = (MeshGeomComp2VB) texCoord2vbMap.get(texCoordOffset);
  94                 assert mt2vb != null;
  95                 // mt2vb shouldn't be null. We can't have a texCoord referred by 
  96                 // the faces array that isn't in the vertexBuffer.
  97                 if (mt2vb != null) {
  98                     int[] locs = mt2vb.getLocs();
  99                     int validLocs = mt2vb.getValidLocs();
 100                     if (locs != null) {
 101                         for (int j = 0; j < validLocs; j++) {
 102                             int vbIndex = (locs[j] * VERTEX_SIZE_VB) + POINT_SIZE_VB;
 103                             vertexBuffer[vbIndex] = uv[texCoordOffset];
 104                             vertexBuffer[vbIndex + 1] = uv[texCoordOffset + 1];
 105                         }
 106                     } else {
 107                         int loc = mt2vb.getLoc();
 108                         int vbIndex = (loc * VERTEX_SIZE_VB) + POINT_SIZE_VB;
 109                         vertexBuffer[vbIndex] = uv[texCoordOffset];
 110                         vertexBuffer[vbIndex + 1] = uv[texCoordOffset + 1];
 111                     }                    
 112                 }
 113             }
 114         }
 115 
 116         // Find out the list of modified points
 117         int startPoint = posFromAndLengthIndices[0] / POINT_SIZE;
 118         int numPoints = (posFromAndLengthIndices[1] / POINT_SIZE);
 119         if ((posFromAndLengthIndices[1] % POINT_SIZE) > 0) {
 120             numPoints++;
 121         }
 122 
 123         if (numPoints > 0) {
 124             for (int i = 0; i < numPoints; i++) {
 125                 int pointOffset = (startPoint + i) * POINT_SIZE;
 126                 MeshGeomComp2VB mp2vb = (MeshGeomComp2VB) point2vbMap.get(pointOffset);
 127                 assert mp2vb != null;
 128                 // mp2vb shouldn't be null. We can't have a point referred by
 129                 // the faces array that isn't in the vertexBuffer.
 130                 if (mp2vb != null) {
 131                     int[] locs = mp2vb.getLocs();
 132                     int validLocs = mp2vb.getValidLocs();
 133                     if (locs != null) {
 134                         for (int j = 0; j < validLocs; j++) {
 135                             int vbIndex = locs[j] * VERTEX_SIZE_VB;
 136                             vertexBuffer[vbIndex] = pos[pointOffset];
 137                             vertexBuffer[vbIndex + 1] = pos[pointOffset + 1];
 138                             vertexBuffer[vbIndex + 2] = pos[pointOffset + 2];
 139                         }
 140                     } else {
 141                         int loc = mp2vb.getLoc();
 142                         int vbIndex = loc * VERTEX_SIZE_VB;
 143                             vertexBuffer[vbIndex] = pos[pointOffset];
 144                             vertexBuffer[vbIndex + 1] = pos[pointOffset + 1];
 145                             vertexBuffer[vbIndex + 2] = pos[pointOffset + 2];
 146                     }                    
 147                 }
 148             }
 149         }
 150 
 151         if (indexBuffer != null) {
 152             return buildNativeGeometry(vertexBuffer,
 153                     numberOfVertices * VERTEX_SIZE_VB, indexBuffer, nFaces * 3);
 154         } else {
 155             return buildNativeGeometry(vertexBuffer,
 156                     numberOfVertices * VERTEX_SIZE_VB, indexBufferShort, nFaces * 3);
 157         }        
 158     }
 159 
 160     private boolean[] dirtyVertices;
 161     private float[] cachedNormals;
 162     private float[] cachedTangents;
 163     private float[] cachedBitangents;
 164     private float[] vertexBuffer;
 165     private int[] indexBuffer;
 166     private short[] indexBufferShort;
 167     private int indexBufferSize;
 168     private int numberOfVertices;
 169 
 170     private HashMap<Integer, MeshGeomComp2VB> point2vbMap;
 171     private HashMap<Integer, MeshGeomComp2VB> normal2vbMap;
 172     private HashMap<Integer, MeshGeomComp2VB> texCoord2vbMap;
 173     
 174     private boolean buildSkipMeshNormalGeometry() { 
 175             
 176         HashMap<Long, Integer> face2vbMap = new HashMap();
 177 
 178         if (point2vbMap == null) {
 179             point2vbMap = new HashMap();
 180         } else {
 181             point2vbMap.clear();
 182         }
 183         if (texCoord2vbMap == null) {
 184             texCoord2vbMap = new HashMap();
 185         } else {
 186             texCoord2vbMap.clear();
 187         }
 188         
 189         Integer mf2vb;
 190         BaseMesh.MeshGeomComp2VB mp2vb;
 191         BaseMesh.MeshGeomComp2VB mt2vb;
 192         vertexBuffer = new float[nVerts * VERTEX_SIZE_VB];
 193         indexBuffer = new int[nFaces * 3];
 194         int ibCount = 0;
 195         int vbCount = 0;
 196 
 197         for (int faceCount = 0; faceCount < nFaces; faceCount++) {
 198             int faceIndex = faceCount * 6;
 199             for (int i = 0; i < 3; i++) {
 200                 int vertexIndex = i * 2;
 201                 int pointIndex = faceIndex + vertexIndex;
 202                 int texCoordIndex = pointIndex + 1;
 203                 long key = (long) ((long) (faces[pointIndex]) << 32 | faces[texCoordIndex]);
 204                 mf2vb = (Integer) face2vbMap.get(key);
 205                 if (mf2vb == null) {
 206                     mf2vb = vbCount / VERTEX_SIZE_VB;
 207 
 208                     face2vbMap.put(key, mf2vb);
 209                     if (vertexBuffer.length <= vbCount) {
 210                         float[] temp = new float[vbCount + 10 * VERTEX_SIZE_VB]; // Let's increment by 10
 211                         System.arraycopy(vertexBuffer, 0, temp, 0, vertexBuffer.length);
 212                         vertexBuffer = temp;
 213                     }
 214                     int pointOffset = faces[pointIndex] * POINT_SIZE;
 215                     int texCoordOffset = faces[texCoordIndex] * TEXCOORD_SIZE;
 216                     vertexBuffer[vbCount] = pos[pointOffset];
 217                     vertexBuffer[vbCount + 1] = pos[pointOffset + 1];
 218                     vertexBuffer[vbCount + 2] = pos[pointOffset + 2];
 219                     vertexBuffer[vbCount + 3] = uv[texCoordOffset];
 220                     vertexBuffer[vbCount + 4] = uv[texCoordOffset + 1];
 221                     vertexBuffer[vbCount + 5] = 0;
 222                     vertexBuffer[vbCount + 6] = 0;
 223                     vertexBuffer[vbCount + 7] = 0;
 224                     vertexBuffer[vbCount + 8] = 0;                   
 225                     vbCount += VERTEX_SIZE_VB;
 226  
 227                     mp2vb = point2vbMap.get(pointOffset);
 228                     if (mp2vb == null) {
 229                         // create 
 230                         mp2vb = new MeshGeomComp2VB(pointOffset, mf2vb);
 231                         point2vbMap.put(pointOffset, mp2vb);
 232                     } else {
 233                         // addLoc
 234                         mp2vb.addLoc(mf2vb);
 235                     }
 236                     
 237                     mt2vb = texCoord2vbMap.get(texCoordOffset);
 238                     if (mt2vb == null) {
 239                         // create 
 240                         mt2vb = new MeshGeomComp2VB(texCoordOffset, mf2vb);
 241                         texCoord2vbMap.put(texCoordOffset, mt2vb);
 242                     } else {
 243                         // addLoc
 244                         mt2vb.addLoc(mf2vb);
 245                     }
 246                 }
 247                 
 248                 // Construct IndexBuffer
 249                 indexBuffer[ibCount++] = mf2vb;
 250             }
 251         }
 252 
 253         numberOfVertices = vbCount / VERTEX_SIZE_VB;
 254         
 255         if (numberOfVertices > 0x10000) { // > 64K
 256             return buildNativeGeometry(vertexBuffer,
 257                     numberOfVertices * VERTEX_SIZE_VB, indexBuffer, nFaces * 3);
 258         } else {
 259              
 260             if (indexBufferShort == null || indexBufferShort.length < nFaces * 3) {
 261                 indexBufferShort = new short[nFaces * 3];
 262             }
 263             int ii = 0;
 264             for (int i = 0; i < nFaces; i++) {
 265                 indexBufferShort[ii] = (short) indexBuffer[ii++]; 
 266                 indexBufferShort[ii] = (short) indexBuffer[ii++]; 
 267                 indexBufferShort[ii] = (short) indexBuffer[ii++]; 
 268             }
 269             indexBuffer = null; // free 
 270             return buildNativeGeometry(vertexBuffer,
 271                     numberOfVertices * VERTEX_SIZE_VB, indexBufferShort, nFaces * 3);
 272         }                
 273     }
 274 
 275     private void convertNormalsToQuats(MeshTempState instance, int numberOfVertices,
 276             float[] normals, float[] tangents, float[] bitangents,
 277             float[] vertexBuffer, boolean[] dirtys) {
 278 
 279         Vec3f normal = instance.vec3f1;
 280         Vec3f tangent = instance.vec3f2;
 281         Vec3f bitangent = instance.vec3f3;
 282         for (int i = 0, vbIndex = 0; i < numberOfVertices; i++, vbIndex += VERTEX_SIZE_VB) {
 283             // Note: If dirtys isn't null, dirtys.length == numberOfVertices is true
 284             if (dirtys == null || dirtys[i]) {
 285                 int index = i * NORMAL_SIZE;
 286 
 287                 normal.x = normals[index];
 288                 normal.y = normals[index + 1];
 289                 normal.z = normals[index + 2];
 290                 normal.normalize();
 291 
 292                 // tangent and bitangent have been normalized.
 293                 tangent.x = tangents[index];
 294                 tangent.y = tangents[index + 1];
 295                 tangent.z = tangents[index + 2];
 296                 bitangent.x = bitangents[index];
 297                 bitangent.y = bitangents[index + 1];
 298                 bitangent.z = bitangents[index + 2];
 299 
 300                 instance.triNormals[0].set(normal);
 301                 instance.triNormals[1].set(tangent);
 302                 instance.triNormals[2].set(bitangent);
 303                 MeshUtil.fixTSpace(instance.triNormals);
 304                 buildVSQuat(instance.triNormals, instance.quat);
 305 
 306                 vertexBuffer[vbIndex + 5] = instance.quat.x;
 307                 vertexBuffer[vbIndex + 6] = instance.quat.y;
 308                 vertexBuffer[vbIndex + 7] = instance.quat.z;
 309                 vertexBuffer[vbIndex + 8] = instance.quat.w;
 310             }
 311         }
 312     }
 313 
 314     // Build PointNormalTexCoordGeometry
 315     private boolean doBuildPNTGeometry(float[] points, float[] normals,
 316             float[] texCoords, int[] faces) {
 317 
 318         if (point2vbMap == null) {
 319             point2vbMap = new HashMap();
 320         } else {
 321             point2vbMap.clear();
 322         }
 323         if (normal2vbMap == null) {
 324             normal2vbMap = new HashMap();
 325         } else {
 326             normal2vbMap.clear();
 327         }
 328         if (texCoord2vbMap == null) {
 329             texCoord2vbMap = new HashMap();
 330         } else {
 331             texCoord2vbMap.clear();
 332         }
 333 
 334         int vertexIndexSize = VertexFormat.POINT_NORMAL_TEXCOORD.getVertexIndexSize();
 335         int faceIndexSize = vertexIndexSize * 3;
 336         int pointIndexOffset = VertexFormat.POINT_NORMAL_TEXCOORD.getPointIndexOffset();
 337         int normalIndexOffset = VertexFormat.POINT_NORMAL_TEXCOORD.getNormalIndexOffset();
 338         int texCoordIndexOffset = VertexFormat.POINT_NORMAL_TEXCOORD.getTexCoordIndexOffset();
 339 
 340         int numPoints = points.length / POINT_SIZE;
 341         int numNormals = normals.length / NORMAL_SIZE;
 342         int numTexCoords = texCoords.length / TEXCOORD_SIZE;
 343         int numFaces = faces.length / faceIndexSize;
 344         assert numPoints > 0 && numNormals > 0 && numTexCoords > 0 && numFaces > 0;
 345         
 346         Integer mf2vb;
 347         BaseMesh.MeshGeomComp2VB mp2vb;
 348         BaseMesh.MeshGeomComp2VB mn2vb;
 349         BaseMesh.MeshGeomComp2VB mt2vb;
 350         // Allocate an initial size, may grow as we process the faces array.
 351         cachedNormals = new float[numPoints * NORMAL_SIZE];
 352         cachedTangents =  new float[numPoints * NORMAL_SIZE];
 353         cachedBitangents = new float[numPoints * NORMAL_SIZE];
 354         vertexBuffer = new float[numPoints * VERTEX_SIZE_VB];
 355         indexBuffer = new int[numFaces * 3];
 356         int ibCount = 0;
 357         int vbCount = 0;
 358 
 359         MeshTempState instance = MeshTempState.getInstance();
 360         for (int i = 0; i < 3; i++) {
 361             if (instance.triPoints[i] == null) {
 362                 instance.triPoints[i] = new Vec3f();
 363             }
 364             if (instance.triTexCoords[i] == null) {
 365                 instance.triTexCoords[i] = new Vec2f();
 366             }
 367         }
 368         
 369         for (int faceCount = 0; faceCount < numFaces; faceCount++) {
 370             int faceIndex = faceCount * faceIndexSize;
 371             for (int i = 0; i < 3; i++) {
 372                 int vertexIndex = faceIndex + (i * vertexIndexSize);
 373                 int pointIndex = vertexIndex + pointIndexOffset;
 374                 int normalIndex = vertexIndex + normalIndexOffset;
 375                 int texCoordIndex = vertexIndex + texCoordIndexOffset;
 376 
 377                 mf2vb = vbCount / VERTEX_SIZE_VB;
 378 
 379                 if (vertexBuffer.length <= vbCount) {
 380                     final int incrementedSize = vbCount + 20; // Let's increment by 20
 381                     float[] temp = new float[incrementedSize * VERTEX_SIZE_VB]; 
 382                     System.arraycopy(vertexBuffer, 0, temp, 0, vertexBuffer.length);
 383                     vertexBuffer = temp;
 384                     // Enlarge cachedNormals, cachedTangents and cachedBitangents too
 385                     temp = new float[incrementedSize * 3];
 386                     System.arraycopy(cachedNormals, 0, temp, 0, cachedNormals.length);
 387                     cachedNormals = temp;
 388                     temp = new float[incrementedSize * 3];
 389                     System.arraycopy(cachedTangents, 0, temp, 0, cachedTangents.length);
 390                     cachedTangents = temp;
 391                     temp = new float[incrementedSize * 3];
 392                     System.arraycopy(cachedBitangents, 0, temp, 0, cachedBitangents.length);
 393                     cachedBitangents = temp;                        
 394                 }
 395                 int pointOffset = faces[pointIndex] * POINT_SIZE;
 396                 int normalOffset = faces[normalIndex] * NORMAL_SIZE;
 397                 int texCoordOffset = faces[texCoordIndex] * TEXCOORD_SIZE;
 398 
 399                 // Save the vertex of triangle
 400                 instance.triPointIndex[i] = pointOffset;
 401                 instance.triTexCoordIndex[i] = texCoordOffset;
 402                 instance.triVerts[i] = vbCount / VERTEX_SIZE_VB;
 403 
 404                 vertexBuffer[vbCount] = points[pointOffset];
 405                 vertexBuffer[vbCount + 1] = points[pointOffset + 1];
 406                 vertexBuffer[vbCount + 2] = points[pointOffset + 2];
 407                 vertexBuffer[vbCount + 3] = texCoords[texCoordOffset];
 408                 vertexBuffer[vbCount + 4] = texCoords[texCoordOffset + 1];
 409                 // Store the normal in the cachedNormals array
 410                 int index = instance.triVerts[i] * NORMAL_SIZE;
 411                 cachedNormals[index] = normals[normalOffset];
 412                 cachedNormals[index + 1] = normals[normalOffset + 1];
 413                 cachedNormals[index + 2] = normals[normalOffset + 2];
 414 
 415                 vbCount += VERTEX_SIZE_VB;
 416 
 417                 mp2vb = point2vbMap.get(pointOffset);
 418                 if (mp2vb == null) {
 419                     // create
 420                     mp2vb = new MeshGeomComp2VB(pointOffset, mf2vb);
 421                     point2vbMap.put(pointOffset, mp2vb);
 422                 } else {
 423                     // addLoc
 424                     mp2vb.addLoc(mf2vb);
 425                 }
 426 
 427                 mn2vb = normal2vbMap.get(normalOffset);
 428                 if (mn2vb == null) {
 429                     // create
 430                     mn2vb = new MeshGeomComp2VB(normalOffset, mf2vb);
 431                     normal2vbMap.put(normalOffset, mn2vb);
 432                 } else {
 433                     // addLoc
 434                     mn2vb.addLoc(mf2vb);
 435                 }
 436 
 437                 mt2vb = texCoord2vbMap.get(texCoordOffset);
 438                 if (mt2vb == null) {
 439                     // create 
 440                     mt2vb = new MeshGeomComp2VB(texCoordOffset, mf2vb);
 441                     texCoord2vbMap.put(texCoordOffset, mt2vb);
 442                 } else {
 443                     // addLoc
 444                     mt2vb.addLoc(mf2vb);
 445                 }
 446 
 447                 // Construct IndexBuffer
 448                 indexBuffer[ibCount++] = mf2vb;
 449             }
 450 
 451             // This is the best time to compute the tangent and bitangent for each
 452             // of the vertex. Go thro. the 3 vertices of a triangle
 453             for (int i = 0; i < 3; i++) {
 454                 instance.triPoints[i].x = points[instance.triPointIndex[i]];
 455                 instance.triPoints[i].y = points[instance.triPointIndex[i] + 1];
 456                 instance.triPoints[i].z = points[instance.triPointIndex[i] + 2];
 457                 instance.triTexCoords[i].x = texCoords[instance.triTexCoordIndex[i]];
 458                 instance.triTexCoords[i].y = texCoords[instance.triTexCoordIndex[i] + 1];
 459             }
 460 
 461             MeshUtil.computeTBNNormalized(instance.triPoints[0], instance.triPoints[1],
 462                     instance.triPoints[2], instance.triTexCoords[0],
 463                     instance.triTexCoords[1], instance.triTexCoords[2],
 464                     instance.triNormals);
 465 
 466             for (int i = 0; i < 3; i++) {
 467                 int index = instance.triVerts[i] * NORMAL_SIZE;
 468                 cachedTangents[index] = instance.triNormals[1].x;
 469                 cachedTangents[index + 1] = instance.triNormals[1].y;
 470                 cachedTangents[index + 2] = instance.triNormals[1].z;
 471                 cachedBitangents[index] = instance.triNormals[2].x;
 472                 cachedBitangents[index + 1] = instance.triNormals[2].y;
 473                 cachedBitangents[index + 2] = instance.triNormals[2].z;
 474             }
 475 
 476         }
 477 
 478         numberOfVertices = vbCount / VERTEX_SIZE_VB;
 479 
 480         convertNormalsToQuats(instance, numberOfVertices, 
 481                 cachedNormals, cachedTangents, cachedBitangents, vertexBuffer, null);
 482 
 483         indexBufferSize = numFaces * 3;
 484 
 485         if (numberOfVertices > 0x10000) { // > 64K
 486             return buildNativeGeometry(vertexBuffer,
 487                     numberOfVertices * VERTEX_SIZE_VB, indexBuffer, indexBufferSize);
 488         } else {
 489 
 490             if (indexBufferShort == null || indexBufferShort.length < indexBufferSize) {
 491                 indexBufferShort = new short[indexBufferSize];
 492             }
 493             int ii = 0;
 494             for (int i = 0; i < numFaces; i++) {
 495                 indexBufferShort[ii] = (short) indexBuffer[ii++];
 496                 indexBufferShort[ii] = (short) indexBuffer[ii++];
 497                 indexBufferShort[ii] = (short) indexBuffer[ii++];
 498             }
 499             indexBuffer = null; // free 
 500             return buildNativeGeometry(vertexBuffer,
 501                     numberOfVertices * VERTEX_SIZE_VB, indexBufferShort, indexBufferSize);
 502         }
 503     }
 504 
 505     // Update PointNormalTexCoordGeometry
 506     private boolean updatePNTGeometry(float[] points, int[] pointsFromAndLengthIndices,
 507             float[] normals, int[] normalsFromAndLengthIndices,
 508             float[] texCoords, int[] texCoordsFromAndLengthIndices) {
 509 
 510         if (dirtyVertices == null) {
 511             // Create a dirty array of size equal to number of vertices in vertexBuffer.
 512             dirtyVertices = new boolean[numberOfVertices];
 513         }
 514         // Clear dirty array before use.
 515         Arrays.fill(dirtyVertices, false);
 516 
 517         // Find out the list of modified points
 518         int startPoint = pointsFromAndLengthIndices[0] / POINT_SIZE;
 519         int numPoints = (pointsFromAndLengthIndices[1] / POINT_SIZE);
 520         if ((pointsFromAndLengthIndices[1] % POINT_SIZE) > 0) {
 521             numPoints++;
 522         }
 523         if (numPoints > 0) {
 524             for (int i = 0; i < numPoints; i++) {
 525                 int pointOffset = (startPoint + i) * POINT_SIZE;
 526                 MeshGeomComp2VB mp2vb = (MeshGeomComp2VB) point2vbMap.get(pointOffset);
 527                 assert mp2vb != null;
 528                 // mp2vb shouldn't be null. We can't have a point referred by
 529                 // the faces array that isn't in the vertexBuffer.
 530                 if (mp2vb != null) {
 531                     int[] locs = mp2vb.getLocs();
 532                     int validLocs = mp2vb.getValidLocs();
 533                     if (locs != null) {
 534                         for (int j = 0; j < validLocs; j++) {
 535                             int vbIndex = locs[j] * VERTEX_SIZE_VB;
 536                             vertexBuffer[vbIndex] = points[pointOffset];
 537                             vertexBuffer[vbIndex + 1] = points[pointOffset + 1];
 538                             vertexBuffer[vbIndex + 2] = points[pointOffset + 2];                   
 539                             dirtyVertices[locs[j]] = true;
 540                         }
 541                     } else {
 542                         int loc = mp2vb.getLoc();
 543                         int vbIndex = loc * VERTEX_SIZE_VB;
 544                         vertexBuffer[vbIndex] = points[pointOffset];
 545                         vertexBuffer[vbIndex + 1] = points[pointOffset + 1];
 546                         vertexBuffer[vbIndex + 2] = points[pointOffset + 2];                    
 547                         dirtyVertices[loc] = true;
 548                     }
 549                 }
 550             }
 551         }
 552 
 553         // Find out the list of modified tex coords.
 554         int startTexCoord = texCoordsFromAndLengthIndices[0] / TEXCOORD_SIZE;
 555         int numTexCoords = (texCoordsFromAndLengthIndices[1] / TEXCOORD_SIZE);
 556         if ((texCoordsFromAndLengthIndices[1] % TEXCOORD_SIZE) > 0) {
 557             numTexCoords++;
 558         }
 559         if (numTexCoords > 0) {
 560             for (int i = 0; i < numTexCoords; i++) {
 561                 int texCoordOffset = (startTexCoord + i) * TEXCOORD_SIZE;
 562                 MeshGeomComp2VB mt2vb = (MeshGeomComp2VB) texCoord2vbMap.get(texCoordOffset);
 563                 assert mt2vb != null;
 564                 // mt2vb shouldn't be null. We can't have a texCoord referred by 
 565                 // the faces array that isn't in the vertexBuffer.
 566                 if (mt2vb != null) {
 567                     int[] locs = mt2vb.getLocs();
 568                     int validLocs = mt2vb.getValidLocs();
 569                     if (locs != null) {
 570                         for (int j = 0; j < validLocs; j++) {
 571                             int vbIndex = (locs[j] * VERTEX_SIZE_VB) + POINT_SIZE_VB;
 572                             vertexBuffer[vbIndex] = texCoords[texCoordOffset];
 573                             vertexBuffer[vbIndex + 1] = texCoords[texCoordOffset + 1];
 574                             dirtyVertices[locs[j]] = true;
 575                         }
 576                     } else {
 577                         int loc = mt2vb.getLoc();
 578                         int vbIndex = (loc * VERTEX_SIZE_VB) + POINT_SIZE_VB;
 579                         vertexBuffer[vbIndex] = texCoords[texCoordOffset];
 580                         vertexBuffer[vbIndex + 1] = texCoords[texCoordOffset + 1];
 581                         dirtyVertices[loc] = true;
 582                     }
 583                 }
 584             }
 585         }
 586 
 587         // Find out the list of modified normals
 588         int startNormal = normalsFromAndLengthIndices[0] / NORMAL_SIZE;
 589         int numNormals = (normalsFromAndLengthIndices[1] / NORMAL_SIZE);
 590         if ((normalsFromAndLengthIndices[1] % NORMAL_SIZE) > 0) {
 591             numNormals++;
 592         }
 593         if (numNormals > 0) {
 594             MeshTempState instance = MeshTempState.getInstance();
 595             for (int i = 0; i < numNormals; i++) {
 596                 int normalOffset = (startNormal + i) * NORMAL_SIZE;
 597                 MeshGeomComp2VB mn2vb = (MeshGeomComp2VB) normal2vbMap.get(normalOffset);
 598                 assert mn2vb != null;
 599                 // mn2vb shouldn't be null. We can't have a normal referred by
 600                 // the faces array that isn't in the vertexBuffer.
 601                 if (mn2vb != null) {
 602                     int[] locs = mn2vb.getLocs();
 603                     int validLocs = mn2vb.getValidLocs();
 604                     if (locs != null) {
 605                         for (int j = 0; j < validLocs; j++) {
 606                             int index = locs[j] * NORMAL_SIZE;
 607                             cachedNormals[index] = normals[normalOffset];
 608                             cachedNormals[index + 1] = normals[normalOffset + 1];
 609                             cachedNormals[index + 2] = normals[normalOffset + 2];
 610                             dirtyVertices[locs[j]] = true;
 611                         }
 612                     } else {
 613                         int loc = mn2vb.getLoc();
 614                         int index = loc * NORMAL_SIZE;
 615                             cachedNormals[index] = normals[normalOffset];
 616                             cachedNormals[index + 1] = normals[normalOffset + 1];
 617                             cachedNormals[index + 2] = normals[normalOffset + 2];
 618                             dirtyVertices[loc] = true;
 619                     }
 620                 }
 621             }
 622         }
 623 
 624         // Prepare process all dirty vertices
 625         MeshTempState instance = MeshTempState.getInstance();
 626         for (int i = 0; i < 3; i++) {
 627             if (instance.triPoints[i] == null) {
 628                 instance.triPoints[i] = new Vec3f();        
 629             }
 630             if (instance.triTexCoords[i] == null) {
 631                 instance.triTexCoords[i] = new Vec2f();
 632             }
 633         }
 634         // Every 3 vertices form a triangle
 635         for (int j = 0; j < numberOfVertices; j += 3) {
 636             // Only process the triangle that has one of more dirty vertices
 637             if (dirtyVertices[j] || dirtyVertices[j+1] || dirtyVertices[j+2]) {                    
 638                 int vbIndex = j * VERTEX_SIZE_VB;
 639                 // Go thro. the 3 vertices of a triangle
 640                 for (int i = 0; i < 3; i++) {
 641                     instance.triPoints[i].x = vertexBuffer[vbIndex];
 642                     instance.triPoints[i].y = vertexBuffer[vbIndex + 1];
 643                     instance.triPoints[i].z = vertexBuffer[vbIndex + 2];
 644                     instance.triTexCoords[i].x = vertexBuffer[vbIndex + POINT_SIZE_VB];
 645                     instance.triTexCoords[i].y = vertexBuffer[vbIndex + POINT_SIZE_VB + 1];
 646                     vbIndex += VERTEX_SIZE_VB;
 647                 }
 648 
 649                 MeshUtil.computeTBNNormalized(instance.triPoints[0], instance.triPoints[1],
 650                         instance.triPoints[2], instance.triTexCoords[0],
 651                         instance.triTexCoords[1], instance.triTexCoords[2],
 652                         instance.triNormals);
 653 
 654                 int index = j * NORMAL_SIZE;
 655                 for (int i = 0; i < 3; i++) {
 656                     cachedTangents[index] = instance.triNormals[1].x;
 657                     cachedTangents[index + 1] = instance.triNormals[1].y;
 658                     cachedTangents[index + 2] = instance.triNormals[1].z;
 659                     cachedBitangents[index] = instance.triNormals[2].x;
 660                     cachedBitangents[index + 1] = instance.triNormals[2].y;
 661                     cachedBitangents[index + 2] = instance.triNormals[2].z;
 662                     index += NORMAL_SIZE;
 663                 }
 664 
 665             }
 666         }
 667 
 668         convertNormalsToQuats(instance, numberOfVertices, 
 669                 cachedNormals, cachedTangents, cachedBitangents, vertexBuffer, dirtyVertices);
 670 
 671         if (indexBuffer != null) {
 672             return buildNativeGeometry(vertexBuffer,
 673                     numberOfVertices * VERTEX_SIZE_VB, indexBuffer, indexBufferSize);
 674         } else {
 675             return buildNativeGeometry(vertexBuffer,
 676                     numberOfVertices * VERTEX_SIZE_VB, indexBufferShort, indexBufferSize);
 677         }
 678 
 679     }
 680 
 681     @Override
 682     public boolean buildGeometry(boolean userDefinedNormals,
 683             float[] points, int[] pointsFromAndLengthIndices,
 684             float[] normals, int[] normalsFromAndLengthIndices,
 685             float[] texCoords, int[] texCoordsFromAndLengthIndices,
 686             int[] faces, int[] facesFromAndLengthIndices,
 687             int[] faceSmoothingGroups, int[] faceSmoothingGroupsFromAndLengthIndices) {
 688         if (userDefinedNormals) {
 689             return buildPNTGeometry(points, pointsFromAndLengthIndices,
 690                     normals, normalsFromAndLengthIndices,
 691                     texCoords, texCoordsFromAndLengthIndices,
 692                     faces, facesFromAndLengthIndices);
 693         } else {
 694             return buildPTGeometry(points, pointsFromAndLengthIndices,
 695                     texCoords, texCoordsFromAndLengthIndices,
 696                     faces, facesFromAndLengthIndices,
 697                     faceSmoothingGroups, faceSmoothingGroupsFromAndLengthIndices);
 698         }
 699     }
 700 
 701     // Build PointNormalTexCoordGeometry
 702     private boolean buildPNTGeometry(
 703             float[] points, int[] pointsFromAndLengthIndices,
 704             float[] normals, int[] normalsFromAndLengthIndices,
 705             float[] texCoords, int[] texCoordsFromAndLengthIndices,
 706             int[] faces, int[] facesFromAndLengthIndices) {
 707 
 708         boolean updatePoints = pointsFromAndLengthIndices[1] > 0;
 709         boolean updateNormals = normalsFromAndLengthIndices[1] > 0;
 710         boolean updateTexCoords = texCoordsFromAndLengthIndices[1] > 0;
 711         boolean updateFaces = facesFromAndLengthIndices[1] > 0;
 712 
 713         // First time creation
 714         boolean buildGeom = !(updatePoints || updateNormals || updateTexCoords || updateFaces);
 715 
 716         // We will need to rebuild geom buffers if there is a change to faces
 717         if (updateFaces) {
 718             buildGeom = true;
 719         }
 720         
 721         if ((!buildGeom) && (vertexBuffer != null)
 722                 && ((indexBuffer != null) || (indexBufferShort != null))) {
 723             return updatePNTGeometry(points, pointsFromAndLengthIndices,
 724                     normals, normalsFromAndLengthIndices,
 725                     texCoords, texCoordsFromAndLengthIndices);
 726         }
 727         return doBuildPNTGeometry(points, normals, texCoords, faces);
 728         
 729     }
 730 
 731     // Build PointTexCoordGeometry
 732     private boolean buildPTGeometry(float[] pos, int[] posFromAndLengthIndices,
 733             float[] uv, int[] uvFromAndLengthIndices,
 734             int[] faces, int[] facesFromAndLengthIndices,
 735             int[] smoothing, int[] smoothingFromAndLengthIndices) {
 736         nVerts = pos.length / 3;
 737         nTVerts = uv.length / 2;
 738         nFaces = faces.length / (VertexFormat.POINT_TEXCOORD.getVertexIndexSize() * 3);
 739         assert nVerts > 0 && nFaces > 0 && nTVerts > 0;
 740         this.pos = pos;
 741         this.uv = uv;
 742         this.faces = faces;
 743         this.smoothing = smoothing.length == nFaces ? smoothing : null;
 744 
 745         if (PrismSettings.skipMeshNormalComputation) {
 746             boolean updatePoints = posFromAndLengthIndices[1] > 0;
 747             boolean updateTexCoords = uvFromAndLengthIndices[1] > 0;
 748             boolean updateFaces = facesFromAndLengthIndices[1] > 0;
 749             boolean updateSmoothing = smoothingFromAndLengthIndices[1] > 0;
 750 
 751             // First time creation
 752             boolean buildGeom = !(updatePoints || updateTexCoords || updateFaces || updateSmoothing);
 753 
 754             // We will need to rebuild if there is a change to faces or smoothing
 755             if (updateFaces || updateSmoothing) {
 756                 buildGeom = true;
 757             }
 758 
 759             if ((!buildGeom) && (vertexBuffer != null)
 760                     && ((indexBuffer != null) || (indexBufferShort != null))) {
 761                 return updateSkipMeshNormalGeometry(posFromAndLengthIndices, uvFromAndLengthIndices);
 762             }
 763 
 764             return buildSkipMeshNormalGeometry();
 765         }
 766 
 767         MeshTempState instance = MeshTempState.getInstance();
 768         // big pool for all possible vertices
 769         if (instance.pool == null || instance.pool.length < nFaces * 3) {            
 770             instance.pool = new MeshVertex[nFaces * 3];
 771         }
 772 
 773         if (instance.indexBuffer == null || instance.indexBuffer.length < nFaces * 3) {
 774             instance.indexBuffer = new int[nFaces * 3];
 775         }
 776 
 777         if (instance.pVertex == null || instance.pVertex.length < nVerts) {
 778             instance.pVertex = new MeshVertex[nVerts];
 779         } else {
 780             Arrays.fill(instance.pVertex, 0, instance.pVertex.length, null);
 781         }
 782              
 783         // check if all hard edges or all smooth  
 784         checkSmoothingGroup();
 785 
 786         // compute [N, T, B] for each face
 787         computeTBNormal(instance.pool, instance.pVertex, instance.indexBuffer);
 788 
 789         // process sm and weld points
 790         int nNewVerts = MeshVertex.processVertices(instance.pVertex, nVerts,
 791                 allHardEdges, allSameSmoothing);
 792         
 793         if (instance.vertexBuffer == null
 794                 || instance.vertexBuffer.length < nNewVerts * VERTEX_SIZE_VB) {
 795             instance.vertexBuffer = new float[nNewVerts * VERTEX_SIZE_VB];
 796         }
 797         buildVertexBuffer(instance.pVertex, instance.vertexBuffer);
 798         
 799         if (nNewVerts > 0x10000) {
 800             buildIndexBuffer(instance.pool, instance.indexBuffer, null);
 801             return buildNativeGeometry(instance.vertexBuffer,
 802                     nNewVerts * VERTEX_SIZE_VB, instance.indexBuffer, nFaces * 3);
 803         } else {
 804             if (instance.indexBufferShort == null || instance.indexBufferShort.length < nFaces * 3) {
 805                 instance.indexBufferShort = new short[nFaces * 3];
 806             }
 807             buildIndexBuffer(instance.pool, instance.indexBuffer, instance.indexBufferShort);
 808             return buildNativeGeometry(instance.vertexBuffer,
 809                     nNewVerts * VERTEX_SIZE_VB, instance.indexBufferShort, nFaces * 3);
 810         }
 811     }
 812 
 813     private void computeTBNormal(MeshVertex[] pool, MeshVertex[] pVertex, int[] indexBuffer) {
 814         MeshTempState instance = MeshTempState.getInstance();
 815         
 816         // tmp variables
 817         int[] smFace = instance.smFace;
 818         int[] triVerts = instance.triVerts;
 819         Vec3f[] triPoints = instance.triPoints;
 820         Vec2f[] triTexCoords = instance.triTexCoords;
 821         Vec3f[] triNormals = instance.triNormals;
 822         final String logname = BaseMesh.class.getName(); 
 823 
 824         for (int f = 0, nDeadFaces = 0, poolIndex = 0; f < nFaces; f++) {
 825             int index = f * 3;
 826 
 827             smFace = getFace(f, smFace); // copy from mesh to tmp smFace
 828 
 829             // Get tex. point. index
 830             triVerts[0] = smFace[BaseMesh.FaceMembers.POINT0.ordinal()];
 831             triVerts[1] = smFace[BaseMesh.FaceMembers.POINT1.ordinal()];
 832             triVerts[2] = smFace[BaseMesh.FaceMembers.POINT2.ordinal()];
 833 
 834             if (MeshUtil.isDeadFace(triVerts)
 835                     && PlatformLogger.getLogger(logname).isLoggable(PlatformLogger.Level.FINE)) {
 836                 // Log degenerated triangle
 837                 nDeadFaces++;
 838                 PlatformLogger.getLogger(logname).fine("Dead face ["
 839                         + triVerts[0] + ", " + triVerts[1] + ", " + triVerts[2]
 840                         + "] @ face group " + f + "; nEmptyFaces = " + nDeadFaces);
 841             }
 842 
 843             for (int i = 0; i < 3; i++) {
 844                 triPoints[i] = getVertex(triVerts[i], triPoints[i]);
 845             }
 846 
 847             // Get tex. coord. index
 848             triVerts[0] = smFace[BaseMesh.FaceMembers.TEXCOORD0.ordinal()];
 849             triVerts[1] = smFace[BaseMesh.FaceMembers.TEXCOORD1.ordinal()];
 850             triVerts[2] = smFace[BaseMesh.FaceMembers.TEXCOORD2.ordinal()];
 851 
 852             for (int i = 0; i < 3; i++) {
 853                 triTexCoords[i] = getTVertex(triVerts[i], triTexCoords[i]);
 854             }
 855 
 856             MeshUtil.computeTBNNormalized(triPoints[0], triPoints[1], triPoints[2],
 857                                           triTexCoords[0], triTexCoords[1], triTexCoords[2],
 858                                           triNormals);
 859 
 860             for (int j = 0; j < 3; ++j) {
 861                 pool[poolIndex] = (pool[poolIndex] == null) ? new MeshVertex() : pool[poolIndex];
 862 
 863                 for (int i = 0; i < 3; ++i) {
 864                     pool[poolIndex].norm[i].set(triNormals[i]);
 865                 }
 866                 pool[poolIndex].smGroup = smFace[BaseMesh.FaceMembers.SMOOTHING_GROUP.ordinal()];
 867                 pool[poolIndex].fIdx = f;
 868                 pool[poolIndex].tVert = triVerts[j];
 869                 pool[poolIndex].index = MeshVertex.IDX_UNDEFINED;
 870                 int ii = j == 0 ? BaseMesh.FaceMembers.POINT0.ordinal()
 871                         : j == 1 ? BaseMesh.FaceMembers.POINT1.ordinal()
 872                         : BaseMesh.FaceMembers.POINT2.ordinal();
 873                 int pIdx = smFace[ii];
 874                 pool[poolIndex].pVert = pIdx;
 875                 indexBuffer[index + j] = pIdx;
 876                 pool[poolIndex].next = pVertex[pIdx];
 877                 pVertex[pIdx] = pool[poolIndex];
 878                 poolIndex++;
 879             }
 880         }
 881     }
 882 
 883     private void buildVSQuat(Vec3f[] tm, Quat4f quat) {
 884         Vec3f v = MeshTempState.getInstance().vec3f1;
 885         v.cross(tm[1], tm[2]);
 886         float d = tm[0].dot(v);
 887         if (d < 0) {
 888             tm[2].mul(-1);
 889         }
 890 
 891         MeshUtil.buildQuat(tm, quat);
 892 
 893         // This will interfer with degenerated triangle unit test. 
 894         // assert (quat.w >= 0);
 895 
 896         if (d < 0) {
 897             if (quat.w == 0) {
 898                 quat.w = MeshUtil.MAGIC_SMALL;
 899             }
 900             quat.scale(-1);
 901         }
 902     }
 903 
 904     private void buildVertexBuffer(MeshVertex[] pVerts, float[] vertexBuffer) {
 905         Quat4f quat = MeshTempState.getInstance().quat;
 906         int idLast = 0;
 907 
 908         for (int i = 0, index = 0; i < nVerts; ++i) {
 909             MeshVertex v = pVerts[i];
 910             for (; v != null; v = v.next) {
 911                 if (v.index == idLast) {
 912                     int ind = v.pVert * 3;
 913                     vertexBuffer[index++] = pos[ind];
 914                     vertexBuffer[index++] = pos[ind + 1];
 915                     vertexBuffer[index++] = pos[ind + 2];
 916                     ind = v.tVert * 2;
 917                     vertexBuffer[index++] = uv[ind];
 918                     vertexBuffer[index++] = uv[ind + 1];
 919                     buildVSQuat(v.norm, quat);
 920                     vertexBuffer[index++] = quat.x;
 921                     vertexBuffer[index++] = quat.y;
 922                     vertexBuffer[index++] = quat.z;
 923                     vertexBuffer[index++] = quat.w;
 924                     idLast++;
 925                 }
 926             }
 927         }
 928     }
 929 
 930     private void buildIndexBuffer(MeshVertex[] pool, int[] indexBuffer, short[] indexBufferShort) {
 931         for (int i = 0; i < nFaces; ++i) {
 932             int index = i * 3;
 933             if (indexBuffer[index] != MeshVertex.IDX_UNDEFINED) {
 934                 for (int j = 0; j < 3; ++j) {
 935                     assert (pool[index].fIdx == i);
 936                     if (indexBufferShort != null) {
 937                         indexBufferShort[index + j] = (short) pool[index + j].index;
 938                     } else {
 939                         indexBuffer[index + j] = pool[index + j].index;
 940                     }
 941                     pool[index + j].next = null; // release reference
 942                 }
 943             } else {
 944                 for (int j = 0; j < 3; ++j) {
 945                     if (indexBufferShort != null) {
 946                         indexBufferShort[index + j] = 0;
 947                     } else {
 948                         indexBuffer[index + j] = 0;
 949                     }
 950                 }
 951             }
 952         }
 953     }
 954     
 955     public int getNumVerts() {
 956         return nVerts;
 957     }
 958 
 959     public int getNumTVerts() {
 960         return nTVerts;
 961     }
 962 
 963     public int getNumFaces() {
 964         return nFaces;
 965     }
 966 
 967     public Vec3f getVertex(int pIdx, Vec3f vertex) {
 968         if (vertex == null) {
 969             vertex = new Vec3f();
 970         }
 971         int index = pIdx * 3;
 972         vertex.set(pos[index], pos[index + 1], pos[index + 2]);
 973         return vertex;
 974     }
 975 
 976     public Vec2f getTVertex(int tIdx, Vec2f texCoord) {
 977         if (texCoord == null) {
 978             texCoord = new Vec2f();
 979         }
 980         int index = tIdx * 2;
 981         texCoord.set(uv[index], uv[index + 1]);
 982         return texCoord;
 983     }
 984 
 985     private void checkSmoothingGroup() {
 986         if (smoothing == null || smoothing.length == 0) { // all smooth
 987             allSameSmoothing = true;
 988             allHardEdges = false;
 989             return;
 990         }
 991 
 992         for (int i = 0; i + 1 < smoothing.length; i++) {
 993             if (smoothing[i] != smoothing[i + 1]) {
 994                 // various SmGroup
 995                 allSameSmoothing = false;
 996                 allHardEdges = false;
 997                 return;
 998             }
 999         }
1000 
1001         if (smoothing[0] == 0) { // all hard edges
1002             allSameSmoothing = false;
1003             allHardEdges = true;
1004         } else { // all belongs to one group == all smooth
1005             allSameSmoothing = true;
1006             allHardEdges = false;
1007         }
1008     }
1009 
1010     public int[] getFace(int fIdx, int[] face) {
1011         int index = fIdx * 6;
1012         if ((face == null) || (face.length < FACE_MEMBERS_SIZE)) {
1013             face = new int[FACE_MEMBERS_SIZE];
1014         }
1015         // Note: Order matter, [0, 5] == FaceMembers' points and texcoords
1016         for (int i = 0; i < 6; i++) {
1017             face[i] = faces[index + i];
1018         }
1019         // Note: Order matter, 6 == FaceMembers.SMOOTHING_GROUP.ordinal()
1020         // There is a total of 32 smoothing groups.
1021         // Assign to 1st smoothing group if smoothing is null.
1022         face[6] = smoothing != null ? smoothing[fIdx] : 1;
1023         return face;
1024     }
1025 
1026     class MeshGeomComp2VB {
1027 
1028         private final int key; // point or texCoord index
1029         private final int loc; // the first index into vertex buffer
1030         private int[] locs;
1031         private int validLocs;
1032 
1033         MeshGeomComp2VB(int key, int loc) {
1034             assert loc >= 0;
1035             this.key = key;
1036             this.loc = loc;
1037             locs = null;
1038             validLocs = 0;
1039         }
1040 
1041         void addLoc(int loc) {
1042             if (locs == null) {
1043                 locs = new int[3]; // edge of mesh case
1044                 locs[0] = this.loc;
1045                 locs[1] = loc;
1046                 this.validLocs = 2;
1047             } else if (locs.length > validLocs) {
1048                 locs[validLocs] = loc;
1049                 validLocs++;
1050             } else {
1051                 int[] temp = new int[validLocs * 2];
1052                 System.arraycopy(locs, 0, temp, 0, locs.length);
1053                 locs = temp;
1054                 locs[validLocs] = loc;
1055                 validLocs++;
1056             }
1057         }
1058 
1059         int getKey() {
1060             return key;
1061         }
1062 
1063         int getLoc() {
1064             return loc;
1065         }
1066 
1067         int[] getLocs() {
1068             return locs;
1069         }
1070 
1071         int getValidLocs() {
1072             return validLocs;
1073         }
1074     }
1075 
1076 }